Понимание производительности петель в jvm

Я играю с jmh, а в разделе looping они сказали, что

Вы можете заметить, что чем больше количество повторений, тем ниже "воспринимаемая" стоимость измеряемой операции. До такой степени мы делать каждое дополнение с 1/20 нс, намного больше, чем оборудование может на самом деле делать. Это происходит потому, что цикл сильно разворачивается/конвейерно, и измеряемая операция поднимается из цикла. Мораль: не overuse loops, полагайтесь на JMH, чтобы получить правильное измерение.

Я сам это пробовал

    @Benchmark
    @OperationsPerInvocation(1)
    public int measurewrong_1() {
        return reps(1);
    }      

    @Benchmark
    @OperationsPerInvocation(1000)
    public int measurewrong_1000() {
        return reps(1000);
    }      

и получил следующий результат:

Benchmark                      Mode  Cnt  Score    Error  Units
MyBenchmark.measurewrong_1     avgt   15  2.425 ±  0.137  ns/op
MyBenchmark.measurewrong_1000  avgt   15  0.036 ±  0.001  ns/op

Это действительно показывает, что MyBenchmark.measurewrong_1000 значительно быстрее, чем MyBenchmark.measurewrong_1. Но я не могу понять оптимизацию JVM, чтобы сделать это улучшение производительности.

Что они означают, что цикл разворачивается/конвейерно?

Ответ 1

Развертывание петли делает возможной конвейерную обработку. Таким образом, процессор, пригодный для работы в трубопроводе (например, RISC), может параллельно выполнять развернутый код.

Итак, если ваш процессор способен параллельно выполнять 5 конвейеров, ваш цикл будет разворачиваться следующим образом:

// pseudo code
int pipelines = 5;
for(int i = 0; i < length; i += pipelines){
    s += (x + y);
    s += (x + y);
    s += (x + y);
    s += (x + y);
    s += (x + y);
}

Risc конвейер

IF = инструкция Fetch, ID = декодирование команды, EX = выполнение, MEM = доступ к памяти, WB = запись записи назад

Из Oracle White paper:

... стандартная оптимизация компилятора, которая позволяет ускорить выполнение цикла. Развертка цикла увеличивает размер тела петли, одновременно уменьшая количество итераций. Развертка цикла также повышает эффективность других оптимизаций.

Дополнительная информация о конвейерной обработке: Классический конвейер RISC

Ответ 2

Развертка Loop - это tecnhique для сглаживания итераций нескольких циклов, повторяя тело цикла.
Например. в данном примере

    for (int i = 0; i < reps; i++) {
        s += (x + y);
    }

может быть развернут JIT-компилятором к чему-то вроде

    for (int i = 0; i < reps - 15; i += 16) {
        s += (x + y);
        s += (x + y);
        // ... 16 times ...
        s += (x + y);
    }

Затем тело расширенного цикла можно дополнительно оптимизировать до

    for (int i = 0; i < reps - 15; i += 16) {
        s += 16 * (x + y);
    }

Очевидно, что вычисление 16 * (x + y) намного быстрее, чем вычисление (x + y) 16 раз.

Ответ 3

Loop Pipelining = Консолидация программного обеспечения.

В принципе, это метод, который используется для оптимизации эффективности повторяющихся циклов последовательных, путем выполнения некоторых инструкций в теле цикла - в parrallel.

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

From insidehpc.com:

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

Подробнее здесь: