Ответ 1
Я использовал jmh для анализа этого. Вот мой код:
@OutputTimeUnit(TimeUnit.MICROSECONDS)
@BenchmarkMode(Mode.AverageTime)
@Warmup(iterations = 2, time = 1)
@Measurement(iterations = 3, time = 1)
@State(Scope.Thread)
@Fork(2)
public class Comparison
{
static final int SIZE = 1<<15;
final int[] data = new int[SIZE];
@Setup
public void setup() {
int i = 1;
for (int c = 0; c < SIZE; ++c) data[c] = (i*=611953);
for (int c = 0; c < SIZE; ++c) data[c] = data[c] >= 128? 128 : 127;
}
@GenerateMicroBenchmark
public long sum() {
long sum = 0;
for (int c = 0; c < SIZE; ++c) if (data[c] >= 128) sum += data[c];
return sum;
}
}
Заметьте, что я не использую ни сортировку, ни генерацию случайных чисел; они являются ненужным осложнением. С формулой, используемой в приведенном выше коде:
data[c] = (i*=611953);
Я получаю 132 мкс времени исполнения. Если я прокомментирую строку с участием
data[c] = data[c] >= 128? 128 : 127;
время не меняется вообще. Это устраняет все арифметические соображения и фокусируется на предсказании ветвей. Если я использую
data[c] = 127;
Я получаю 13 мкс, и если я использую
data[c] = 128;
Я получаю 16 мкс. Это "базовый случай", подчеркивающий разницу между постоянными решениями ветвления.
Мой вывод: это определенно эффект предсказания ветвления на низком уровне.
Может ли JIT отменить цикл?
Проанализируйте свое вмешательство сейчас. Если я использую формулу, представленную в моем коде выше, но изменим
if (data[c] >= 128) sum += data[c];
to
if (data[c] >= 128) sum = data[c];
тогда время действительно снижается с 132 мкс до 27 мкс.
Это мое предположение, объясняющее падение: оптимизирующий трюк, который может сделать JIT-компилятор, - это изменить направление цикла. Теперь ваш код станет
for (int c = SIZE-1; c <= 0; --c) if (data[c] >= 128) { sum = data[c]; break; }
цикл был закорочен до минимального количества итераций, необходимых для достижения того же результата, что и исходный цикл.
Я добавил это
data[SIZE-1] = 128;
до конца метода setup()
, но это не изменило время. Это, по-видимому, аннулирует наивный вариант гипотезы об "повороте петли".
Нет, возможно, cmovl
При анализе сборки я нахожу это:
cmp edx, 0x80
cmovl eax, ebx
cmovl
представляет собой инструкцию условного перемещения, которая будет выполнять эффект присваивания, происходящего в ветки then
, но без привлечения каких-либо переходов, поэтому устраняет любое наказание, связанное с отказом предсказания ветвления. Это хорошее объяснение фактического эффекта.