Что такое метод инверсии цикла?

Я просматривал документ, в котором обсуждались методы оптимизации компилятора "точно в срок" (JIT) для Java. Одним из них была "инверсия цикла" . И в документе говорится:

Вы заменяете обычный цикл while контуром do-while. И Цикл do-while устанавливается в предложении if. Эта замена приводит к еще двум прыжкам.

Как работает инверсия цикла и как она оптимизирует наш путь кода?

N.B.: Было бы здорово, если кто-нибудь сможет объяснить пример кода Java и как JIT оптимизирует его для собственного кода и почему он оптимален в современных процессорах.

Ответ 1

while (condition) { 
  ... 
}

Workflow:

  • условие проверки;
  • если false, перейдите в конец цикла;
  • запустить одну итерацию;
  • перейти наверх.

if (condition) do {
  ...
} while (condition);

Workflow:

  • условие проверки;
  • если false, перейдите за пределы цикла;
  • запустить одну итерацию;
  • условие проверки;
  • если true, перейдите к шагу 3.

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

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

Объяснение указанного прогноза ветвления: для каждого типа условного перехода CPU имеет две инструкции, каждая из которых включает ставку на результат. Например, вы должны поставить инструкцию, в которой "jump if not zero, betting on not zero" в конце цикла, потому что скачок должен быть сделан на всех итерациях, кроме последнего. Таким образом, CPU начинает накачивать свой конвейер инструкциями, следующих за целью перехода, вместо тех, которые следуют самой инструкции перехода.

Важное примечание

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

Ответ 2

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

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

int i = 0;
while (i++ < 1) {
    //do something
}  
С другой стороны, цикл

A do-while пропустит первый и последний прыжки. Ниже приведен эквивалентный цикл, приведенный выше, который будет работать без переходов:

int i = 0;
if (i++ < 1) {
    do {
        //do something
    } while (i++ < 1); 
}

Ответ 3

Пройдите через них:

Версия while:

void foo(int n) {
    while (n < 10) {
       use(n);
       ++n;
    }
    done();
}
  • Сначала мы тестируем n и переходим к done();, если это условие неверно.
  • Затем мы используем и увеличиваем n.
  • Теперь мы возвращаемся к условию.
  • Промыть, повторить.
  • Когда условие больше не верно, переходим к done().

Версия do-while:

(Помните, что мы на самом деле не делаем этого в исходном коде [который будет вводить проблемы обслуживания], компилятор /JIT делает это для нас.)

void foo(int n) {
    if (n < 10) {
        do {
            use(n);
            ++n;
        }
        while (n < 10);
    }
    done();
}
  • Сначала мы тестируем n и переходим к done();, если это условие неверно.
  • Затем мы используем и увеличиваем n.
  • Теперь мы проверяем условие и возвращаем назад, если оно истинно.
  • Промыть, повторить.
  • Когда условие больше не верно, мы переходим (не прыгаем) к done().

Так, например, если n начинается с 9, мы никогда не прыгаем вообще в версии do-while, тогда как в версии while нам нужно вернуться к началу, выполнить тест, а затем вернитесь к концу, когда мы увидим, что это неверно.

Ответ 4

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

Эта ссылка является еще одним примером инверсии цикла. В нескольких архитектурах, где декремент и сравнение реализованы как единый набор команд, имеет смысл преобразовать цикл for в какое-то время с помощью операции уменьшения и сравнения.

В Википедии есть очень хороший пример, и я объясняю его здесь снова.

 int i, a[100];
  i = 0;
  while (i < 100) {
    a[i] = 0;
    i++;
  }

будет преобразован компилятором в

  int i, a[100];
  i = 0;
  if (i < 100) {
    do {
      a[i] = 0;
      i++;
    } while (i < 100);
  }

Как это соответствует производительности? Когда значение я равно 99, процессор не должен выполнять GOTO (что требуется в первом случае). Это повышает производительность.