Не работает ли предсказание ветвления?

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

import java.util.Arrays;
import java.util.Random;


public class Main{

    public static void main(String[] args) {
        // Generate data
        int arraySize = 32768;
        int data[] = new int[arraySize];

        Random rnd = new Random(0);
        for (int c = 0; c < arraySize; ++c) {
            data[c] = rnd.nextInt() % 256;
        }

        // !!! With this, the next loop runs faster
        Arrays.sort(data);

        // Test
        long start = System.nanoTime();
        long sum = 0;

        for (int i = 0; i < 100000; ++i) {
            // Primary loop
            for (int c = 0; c < arraySize; ++c) {
                if (data[c] >= 128) {
                    sum = data[c];
                }
            }
        }

        System.out.println((System.nanoTime() - start) / 1000000000.0);
        System.out.println("sum = " + sum);
    }
}

здесь я заменил (из оригинального вопроса)

if (data[c] >= 128) 
    sum += data[c];

с

if (data[c] >= 128) 
    sum = data[c];

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

Ответы

Ответ 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, но без привлечения каких-либо переходов, поэтому устраняет любое наказание, связанное с отказом предсказания ветвления. Это хорошее объяснение фактического эффекта.