Микро-контрольная рамка

Производительность бенчмаркинга небольших функций на Java, как известно, сложна, и есть ряд инструментов для помощи (суппорт, другие). Эти другие инструменты требуют достаточного количества настроек и установки, чтобы заставить их работать.

Этот код адаптирован с учетом вопроса @skiwi . Он создал эту инфраструктуру ProjectEuler, и я адаптировал ее для более удобного мониторинга производительности.

Основная предпосылка заключается в том, что вы хотите запустить метод несколько раз. Некоторые из них - время разминки, а остальные - «настоящие» пробежки. Инструмент выполняет разминки, а затем выполняет реальный пробег. Он усредняет время выполнения для реального прогона для создания времени выполнения для этого метода.

Основная часть логики включена в класс под названием Problem, который имеет execute(), который является абстрактным.

package euler;

public abstract class Problem<T> {
    private final String name;
    private final int warmup;
    private final int realruns;

    public Problem(String name, int warmups, int realruns) {
        this.name = name;
        this.warmup = warmups;
        this.realruns = realruns;
    }

    public String getResult() {
        return String.valueOf(execute());
    }

    public final int getWarmups() {
        return warmup;
    }

    public final int getRealRuns() {
        return realruns;
    }

    public final String getName() {
        return name;
    }

    public abstract T execute();

}

Типичная реализация этой задачи, например, заключается в вычислении среднего значения массива целых чисел, и он будет реализован как:

public class AverageIntegers extends Problem<Double>{

    private static final int[] DATA = {1, 2, 3, 4, 5, 6, 7, 8, 9, 8, 7, 6, 5, 4, 3, 2, 1};

    public AverageIntegers() {
        super("Average Integers", 1000, 10000);
    }

    @Override
    public Double execute() {
        int sum = 0;
        for (int v : DATA) {
            sum += v;
        }
        return sum / (double)DATA.length;
    }
}

В приведенной выше реализации вы можете включить его в класс тестов:

package euler;

import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;

public class ProjectEuler {

    /**
     * @param args
     *            the command line arguments
     */
    public static void main(String[] args) {
        ProjectEuler pe = new ProjectEuler();
        pe.process();
        System.out.println("\n\nWarmup Complete\n\n");
        pe.process();
    }

    private static final int longestName(List<Problem<?>> probs) {
        int namelen = 0;
        for (Problem<?> p : probs) {
            namelen = Math.max(namelen, p.getName().length());
        }
        return namelen;
    }


    private static final double MILLION = 1_000_000.0;


    private final List<Problem<?>> problems = new ArrayList<>();
    private final int longestname;

    public ProjectEuler() {

        /* **********************************
         * ADD YOUR PROBLEMS HERE!
         * ***********************************/

        problems.add(new AverageIntegers());
        // problems.add(new AlternativeImplementation1());
        // problems.add(new AlternativeImplementation2());
        // ....

        longestname = longestName(problems);
    }

    private void process() {
        problems.stream().forEachOrdered(new ProblemConsumer());
    }

    private class ProblemConsumer implements Consumer<Problem<?>> {
        @Override
        public void accept(final Problem<?> problem) {

            final long basetime = System.nanoTime();
            final int wreps = problem.getWarmups();
            final int rreps = problem.getRealRuns();

            long btime = System.nanoTime();
            final String result = problem.getResult();
            btime = System.nanoTime() - btime;
            for (int i = wreps; i > 0; i--) {

                String actual = problem.getResult();
                if (!result.equals(actual)) {
                    throw new IllegalStateException("Unexpected result "
                            + actual);
                }
                ;
            }

            System.gc();

            final long start = System.nanoTime();
            for (int i = rreps; i > 0; i--) {
                problem.execute();
            }
            final long end = System.nanoTime();
            final long elapsed = end - start;

            String actual = problem.getResult();

            System.out.printf("%-" + longestname
                    + "s => %s (hot %.5fms - cold %.3fms (total %.3fms))\n",
                    problem.getName(), actual, (elapsed / MILLION) / rreps,
                    btime / MILLION, (end - basetime) / MILLION);
        }
    }
}
11 голосов | спросил rolfl 2 J0000006Europe/Moscow 2014, 22:10:08

2 ответа


7

Framework

    /* **********************************
     * ADD YOUR PROBLEMS HERE!
     * ***********************************/

Извините, вы просто назвали это каркасом ? Никогда в жизни я не видел рамки, где мне приходилось редактировать исходный код , чтобы сделать его пригодным для использования.

Пусть пользователи структуры передают List<Problem<?>> problems вместо этого.

И пока вы на нем, могу ли я получить общедоступный метод из класса ProjectEuler? process, например, он был бы идеальным как открытый метод.


Нейминг

Класс ProjectEuler не ограничивается только проблемами, реализующими EulerProblem, не так ли? Я уверен, что вы можете найти лучшее имя. ProblemBenchmarker возможно?


Абстрактный класс?

Я не вижу смысла делать Problem абстрактным классом. Лично я считаю, что имеет смысл добавить поле в класс Problem и параметр конструктору:

public Problem(String name, int warmups, int realruns, Supplier<T> supplier) {

И просто измените метод execute, чтобы:

public T execute() {
    return supplier.get();
}

Это предоставит возможность иметь несколько проблем в одном классе, которые IMO предоставляет хороший обзор проблем:

public static void main(String[] args) {
    List<Problem<?>> problems = new ArrayList<>();
    problems.add(new Problem<>("Averaging", 1000, 10000, ProblemMain::problemOne));
    problems.add(new Problem<>("Multiplying", 1000, 10000, ProblemMain::problemTwo));

    ProjectEuler euler = new ProjectEuler(problems);

    euler.process();
    System.out.println("\n\nWarmup Complete\n\n");
    euler.process();

}

private static final int[] DATA = {1, 2, 3, 4, 5, 6, 7, 8, 9, 8, 7, 6, 5, 4, 3, 2, 1};

public static Double problemOne() {
    int sum = 0;
    for (int v : DATA) {
        sum += v;
    }
    return sum / (double)DATA.length;
}

public static Integer problemTwo() {
    int sum = 0;
    for (int v : DATA) {
        sum *= v;
    }
    return sum;
}

Дженерики

Я не уверен, что дженерики класса Problem<T> принесут вам пользу. Он используется в List<Problem<?>> в любом случае, поэтому я не вижу, чтобы тип безопасности дженериков давал вам что угодно. Подумайте об удалении дженериков и используйте вместо него Supplier<?>, а также execute() возвращает Object.

Конечно, было бы аккуратно, если бы можно было избежать автоматического бокса, который Java делает для примитивных значений, но я ожидаю, что для этого потребуется некоторое дублирование кода, и я не уверен, что потенциальная производительность может быть выйти из него стоит код, чтобы добавить его. (Держу пари, вы можете ответить на это лучше, чем я могу, но вы научили меня, что авто-бокс действительно влияет на производительность)


Полезность

В целом, я думаю, что этот код будет чрезвычайно полезным , особенно когда я могу использовать его, не изменяя его источник!

ответил Simon Forsberg 2 J0000006Europe/Moscow 2014, 23:41:31
4
  

Производительность бенчмаркинга небольших функций на Java, как известно, сложна, и есть ряд инструментов для помощи (суппорт, другие). Эти другие инструменты требуют достаточного количества настроек и установки, чтобы заставить их работать.

Это отличная инициатива! Я просто не думаю, что вы это достигаете: для этого все еще требуется достаточное количество настроек, копирования и вставки жесткого кода. Это не очень удобно, поэтому я думаю, что вам не хватает вашей главной цели.

Разделение ответственности

Класс Problem<?> отвечает за слишком много вещей:

  • Представляет предмет теста (предмет, который вы хотите измерить)
  • Отслеживает результат объекта (возвращаемый T of execute())
  • Содержит реализацию объекта (в execute() подклассов)

Самая большая проблема - отслеживание результата. Проверка правильности алгоритма относится к модульным испытаниям, а не к бенчмаркингу.

Реализация алгоритма, на который вы сравниваете, не должна быть принудительно расширена Program<?>, но может свободно стоять сама по себе, не осознавая базовые рамки. Хорошее решение для защиты от кодирования должно выставлять методы, которые позволяют легко тестировать или тестировать в любом случае.

Альтернативный подход

Я думаю о свободно связанной аннотации основе, что-то вроде этого:

@BenchmarkSuite(iterations = 100, warmUp = true)
public class Example {

    public void run(int[] input) {
        // make a call to the solution implementation
    }

    // can override default values set in @BenchmarkSuite
    @MeasureTime(iterations = 3)
    public void largeUnsortedSample() {
        // run(...);
    }

    @MeasureMemory
    public void largeSetWorstCaseScenario() {
        // run(...);
    }
}

Это чрезвычайно гибко, потому что структура не диктует, как вы запускаете код темы, это может быть все, что вам нужно, нет требования к типам классов и сигнатурам методов.

Я создал доказательство концепции, которое сейчас довольно дерьмово, но может быть началом чего-то:

https://github.com/janosgyerik/java-microbench

Чтобы сравнить несколько объектов, я думаю, что следующий интерфейс программирования был бы отличным, если это возможно:

interface Problem {
    void solve(int[] input);
}

@Subject
Problem problem1 = new Problem() {
    @Override
    public void solve(int[] input) {
        // call one implementation
    }
};

@Subject
Problem problem2 = new Problem() {
    @Override
    public void solve(int[] input) {
        // call a different implementation
    }
};

// the framework should alternate the value for each @Subject
// before running each benchmark method
@Work
Problem problem;

public void run(String message) {
    problem.solve(message);
}

То есть, обозначьте объект @Work, так что для каждого @Subject, структура будет чередовать значение, что упростит повторение одних и тех же тестов для нескольких реализаций. Требование состоит в том, чтобы все @Subject реализовали общий интерфейс, соответствующий типу @Work. Что-то вроде того. Сам интерфейс Problem не является частью фреймворка, это всего лишь образец кода.

ответил janos 3 J0000006Europe/Moscow 2014, 15:48:54

Похожие вопросы

Популярные теги

security × 330linux × 316macos × 2827 × 268performance × 244command-line × 241sql-server × 235joomla-3.x × 222java × 189c++ × 186windows × 180cisco × 168bash × 158c# × 142gmail × 139arduino-uno × 139javascript × 134ssh × 133seo × 132mysql × 132