Проверьте измерения JMH простых сравнений ля / лямбда - PullRequest
3 голосов
/ 26 марта 2019

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

Я включаю весь свой класс JMH здесь.

import java.util.ArrayList;
import java.util.List;

import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.Setup;
import org.openjdk.jmh.annotations.State;

@State(Scope.Benchmark)
public class MyBenchmark {
    List<String>    shortLengthListConstantSize     = null;
    List<String>    mediumLengthListConstantSize    = null;
    List<String>    longerLengthListConstantSize    = null;
    List<String>    longLengthListConstantSize      = null;

    @Setup
    public void setup() {
        shortLengthListConstantSize     = populateList(2);
        mediumLengthListConstantSize    = populateList(12);
        longerLengthListConstantSize    = populateList(300);
        longLengthListConstantSize      = populateList(300000);
    }

    private List<String> populateList(int size) {
        List<String> list   = new ArrayList<>();
        for (int ctr = 0; ctr < size; ++ ctr) {
            list.add("xxx");
        }
        return list;
    }

    @Benchmark
    public long shortLengthConstantSizeFor() {
        long count   = 0;
        for (String val : shortLengthListConstantSize) {
            if (val.length() == 3) { ++ count; }
        }
        return count;
    }

    @Benchmark
    public long shortLengthConstantSizeForEach() {
        IntHolder   intHolder   = new IntHolder();
        shortLengthListConstantSize.forEach(s -> { if (s.length() == 3) ++ intHolder.value; } );
        return intHolder.value;
    }

    @Benchmark
    public long shortLengthConstantSizeLambda() {
        return shortLengthListConstantSize.stream().filter(s -> s.length() == 3).count();
    }

    @Benchmark
    public long shortLengthConstantSizeLambdaParallel() {
        return shortLengthListConstantSize.stream().parallel().filter(s -> s.length() == 3).count();
    }

    @Benchmark
    public long mediumLengthConstantSizeFor() {
        long count   = 0;
        for (String val : mediumLengthListConstantSize) {
            if (val.length() == 3) { ++ count; }
        }
        return count;
    }

    @Benchmark
    public long mediumLengthConstantSizeForEach() {
        IntHolder   intHolder   = new IntHolder();
        mediumLengthListConstantSize.forEach(s -> { if (s.length() == 3) ++ intHolder.value; } );
        return intHolder.value;
    }

    @Benchmark
    public long mediumLengthConstantSizeLambda() {
        return mediumLengthListConstantSize.stream().filter(s -> s.length() == 3).count();
    }

    @Benchmark
    public long mediumLengthConstantSizeLambdaParallel() {
        return mediumLengthListConstantSize.stream().parallel().filter(s -> s.length() == 3).count();
    }

    @Benchmark
    public long longerLengthConstantSizeFor() {
        long count   = 0;
        for (String val : longerLengthListConstantSize) {
            if (val.length() == 3) { ++ count; }
        }
        return count;
    }

    @Benchmark
    public long longerLengthConstantSizeForEach() {
        IntHolder   intHolder   = new IntHolder();
        longerLengthListConstantSize.forEach(s -> { if (s.length() == 3) ++ intHolder.value; } );
        return intHolder.value;
    }

    @Benchmark
    public long longerLengthConstantSizeLambda() {
        return longerLengthListConstantSize.stream().filter(s -> s.length() == 3).count();
    }

    @Benchmark
    public long longerLengthConstantSizeLambdaParallel() {
        return longerLengthListConstantSize.stream().parallel().filter(s -> s.length() == 3).count();
    }

    @Benchmark
    public long longLengthConstantSizeFor() {
        long count   = 0;
        for (String val : longLengthListConstantSize) {
            if (val.length() == 3) { ++ count; }
        }
        return count;
    }

    @Benchmark
    public long longLengthConstantSizeForEach() {
        IntHolder   intHolder   = new IntHolder();
        longLengthListConstantSize.forEach(s -> { if (s.length() == 3) ++ intHolder.value; } );
        return intHolder.value;
    }

    @Benchmark
    public long longLengthConstantSizeLambda() {
        return longLengthListConstantSize.stream().filter(s -> s.length() == 3).count();
    }

    @Benchmark
    public long longLengthConstantSizeLambdaParallel() {
        return longLengthListConstantSize.stream().parallel().filter(s -> s.length() == 3).count();
    }

    public static class IntHolder {
        public int value    = 0;
    }
}

Я запускаю их на ноутбуке Win7. Меня не волнуют абсолютные измерения, просто относительные. Вот последние результаты из них:

Benchmark                                            Mode  Cnt          Score         Error  Units
MyBenchmark.longLengthConstantSizeFor               thrpt  200       2984.554 ±      57.557  ops/s
MyBenchmark.longLengthConstantSizeForEach           thrpt  200       2971.701 ±     110.414  ops/s
MyBenchmark.longLengthConstantSizeLambda            thrpt  200        331.741 ±       2.196  ops/s
MyBenchmark.longLengthConstantSizeLambdaParallel    thrpt  200       2827.695 ±     682.662  ops/s
MyBenchmark.longerLengthConstantSizeFor             thrpt  200    3551842.518 ±   42612.744  ops/s
MyBenchmark.longerLengthConstantSizeForEach         thrpt  200    3616285.629 ±   16335.379  ops/s
MyBenchmark.longerLengthConstantSizeLambda          thrpt  200    2791292.093 ±   12207.302  ops/s
MyBenchmark.longerLengthConstantSizeLambdaParallel  thrpt  200      50278.869 ±    1977.648  ops/s
MyBenchmark.mediumLengthConstantSizeFor             thrpt  200   55447999.297 ±  277442.812  ops/s
MyBenchmark.mediumLengthConstantSizeForEach         thrpt  200   57381287.954 ±  362751.975  ops/s
MyBenchmark.mediumLengthConstantSizeLambda          thrpt  200   15925281.039 ±   65707.093  ops/s
MyBenchmark.mediumLengthConstantSizeLambdaParallel  thrpt  200      60082.495 ±     581.405  ops/s
MyBenchmark.shortLengthConstantSizeFor              thrpt  200  132278188.475 ± 1132184.820  ops/s
MyBenchmark.shortLengthConstantSizeForEach          thrpt  200  124158664.044 ± 1112991.883  ops/s
MyBenchmark.shortLengthConstantSizeLambda           thrpt  200   18750818.019 ±  171239.562  ops/s
MyBenchmark.shortLengthConstantSizeLambdaParallel   thrpt  200     474054.951 ±    1344.705  ops/s

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

Еще одна вещь, с которой я всегда был неуверен в выводе JMH, это точное определение числа пропускной способности. Например, что именно означает «200» в столбце «Cnt»? Единицы пропускной способности указаны в «операциях в секунду», так что же именно представляет «операция», это выполнение одного вызова эталонного метода? Например, в последнем ряду это будет представлять 474 тыс. Выполнений метода тестирования в секунду.

Обновление

Замечу, что когда я сравниваю «for» с «lambda», начиная с «короткого» списка и переходя к более длинным спискам, соотношение между ними довольно большое, но уменьшается, пока не появится «длинный» список, где соотношение даже больше, чем для «короткого» списка (14%, 29%, 78% и 11%). Я нахожу это удивительным. Я ожидал бы, что отношение накладных расходов потоков уменьшится, поскольку работа в реальной бизнес-логике увеличивается. У кого-нибудь есть мысли по этому поводу?

1 Ответ

0 голосов
/ 26 марта 2019

Например, что именно означает «200» в столбце «Cnt»?

В столбце cnt указано количество итераций, т. Е. Сколько раз тесты повторяются.Вы можете контролировать это значение, используя следующие аннотации:

  • Для фактических измерений: @Measurement(iterations = 10, time = 50, timeUnit = TimeUnit.MILLISECONDS)
  • Для фазы прогрева: @Warmup(iterations = 10, time = 1, timeUnit = TimeUnit.SECONDS)

Здесь iterations равно cnt;time - требуемая продолжительность одной итерации, а timeUnit - единица измерения значения time.

Единицы пропускной способности указаны в «операциях в секунду»

Вы можете управлять выходом несколькими способами.Например, вы можете изменить единицу измерения времени, используя @OutputTimeUnit(TimeUnit.XXXX), чтобы вы могли получить ops / us, ops / ms

Вы также можете изменить mode: вместо измерения ops / time youможет измерять «среднее время», «время выборки» и т. д. Вы можете контролировать это с помощью @BenchmarkMode({Mode.AverageTime}) аннотации

, так что именно то, что представляет собой «операция», состоит в том, что выполнение одного вызовак методу тестирования

Допустим, одна итерация длится 1 секунду, и вы получаете 1000 операций в секунду.Это означает, что метод benchamrk был выполнен 1000 раз.

Другими словами, одна операция - это одно выполнение метода эталонного теста, если только у вас нет аннотации @OperationsPerInvocation(XXX), что означает, что обучающий вызов методов будет считаться операциями XXX.

Ошибкарассчитывается для всех итераций.


Еще один совет: вместо жесткого кодирования каждого возможного размера вы можете выполнить параметризованный тест:

@Param({"3", "12", "300", "3000"})
private int length;

Затем вы можете использовать этот параметр в вашемнастройка:

 @Setup(Level.Iteration)
 public void setUp(){
     populateList(length)
 }
...