Производительность поиска в массиве по сравнению с битшифтом - PullRequest
0 голосов
/ 21 февраля 2019

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

Вот как я пересчитываю степени двух:

private static final long[] POWERS_OF_TWO = LongStream.range(0L, NUM_SQUARES)
                                                      .map(l -> 1L << l)
                                                      .toArray();

А вот так может выглядеть позывной

final long newSelf = self | cur | POWERS_OF_TWO[i];

Теперь альтернативой может быть непосредственное вычисление мощности двух на месте вызова:

final long newSelf = self | cur | (1L << i);

Какой методбыстрее?

Ответы [ 2 ]

0 голосов
/ 21 февраля 2019

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

Давайте посмотрим на код цикла:

for (int i = 0; i < N; i++) {
    final int idx = i % NUM_SQUARES;
    final long value = 1L << idx;
    sum += value;
}

Сдвиг - это действительно быстрая операция для процессора -это должно быть по крайней мере так же быстро, как ++ или +=.Поэтому, если мы посмотрим на код, у нас есть счетчик циклов (i++), sum += и ветвь (i < N).%, вероятно, занимает в 10-20 раз больше циклов ЦП, чем сдвиг.

Так что для лучшего тестирования и синхронизации операции сдвига я бы удалил % и получил бы все 64 степени двух внутрицикл в случайном порядке (для массива).Примерно так:

for (int i = 0; i < N; i++) {
    sum += 1L << 4;
    sum += 1L << 7;
    sum += 1L << 1;
    // and so on
}

Для версии массива вы можете использовать:

for (int i = 0; i < N; i++) {
    sum += POWER_OF_TWOS[4];
    sum += POWER_OF_TWOS[7];
    sum += POWER_OF_TWOS[1];
    // and so on
}

Или использовать переменные вместо констант и битовую маску вместо%:

for (int i = 0; i < N; i++) {
    final int idx = i & 0x3f;
    sum += 1L << idx;
}

for (int i = 0; i < N; i++) {
    final int idx = i & 0x3f;
    sum += POWER_OF_TWOS[idx];
}

Или, если оптимизатор может быть обманут с этим:

for (int i = 0; i < N; i++) {
    final int idx = 0;
    sum += 1L << (idx + 4);
    sum += 1L << (idx + 17);
    // ...
}

В любом случае, операция сдвига действительно быстра и должна быть легкой в ​​использовании для массива с предкомпьютерными значениями.

0 голосов
/ 21 февраля 2019

Я решил написать тест JMH, чтобы проверить мою проблему.

Тест

import org.openjdk.jmh.Main;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.Fork;
import org.openjdk.jmh.annotations.Measurement;
import org.openjdk.jmh.annotations.Warmup;
import org.openjdk.jmh.runner.RunnerException;

import java.io.IOException;
import java.util.stream.LongStream;

@Warmup(iterations = 3)
@Measurement(iterations = 3)
@Fork(value = 1)
public class LookupVsShift {
    private static final int NUM_SQUARES = 64;
    private static final long[] POWERS_OF_TWO = LongStream.range(0L, NUM_SQUARES)
                                                          .map(l -> 1L << l)
                                                          .toArray();
    private static final int N = 1_000_000;

    @Benchmark
    public long testLookupUnroll() {
        long sum = 0;

        for (int i = 0; i < N; i++) {
            sum += (i + POWERS_OF_TWO[58]) & 0x3f;
            sum += (i + POWERS_OF_TWO[23]) & 0x3f;
            sum += (i + POWERS_OF_TWO[55]) & 0x3f;
            sum += (i + POWERS_OF_TWO[56]) & 0x3f;
            sum += (i + POWERS_OF_TWO[52]) & 0x3f;
            sum += (i + POWERS_OF_TWO[38]) & 0x3f;
            sum += (i + POWERS_OF_TWO[49]) & 0x3f;
            sum += (i + POWERS_OF_TWO[36]) & 0x3f;
            sum += (i + POWERS_OF_TWO[9]) & 0x3f;
            sum += (i + POWERS_OF_TWO[7]) & 0x3f;
            sum += (i + POWERS_OF_TWO[19]) & 0x3f;
            sum += (i + POWERS_OF_TWO[54]) & 0x3f;
            sum += (i + POWERS_OF_TWO[37]) & 0x3f;
            sum += (i + POWERS_OF_TWO[4]) & 0x3f;
            sum += (i + POWERS_OF_TWO[35]) & 0x3f;
            sum += (i + POWERS_OF_TWO[8]) & 0x3f;
            sum += (i + POWERS_OF_TWO[40]) & 0x3f;
            sum += (i + POWERS_OF_TWO[33]) & 0x3f;
            sum += (i + POWERS_OF_TWO[43]) & 0x3f;
            sum += (i + POWERS_OF_TWO[13]) & 0x3f;
            sum += (i + POWERS_OF_TWO[14]) & 0x3f;
            sum += (i + POWERS_OF_TWO[3]) & 0x3f;
            sum += (i + POWERS_OF_TWO[20]) & 0x3f;
            sum += (i + POWERS_OF_TWO[63]) & 0x3f;
            sum += (i + POWERS_OF_TWO[29]) & 0x3f;
            sum += (i + POWERS_OF_TWO[18]) & 0x3f;
            sum += (i + POWERS_OF_TWO[45]) & 0x3f;
            sum += (i + POWERS_OF_TWO[22]) & 0x3f;
            sum += (i + POWERS_OF_TWO[57]) & 0x3f;
            sum += (i + POWERS_OF_TWO[26]) & 0x3f;
            sum += (i + POWERS_OF_TWO[24]) & 0x3f;
            sum += (i + POWERS_OF_TWO[10]) & 0x3f;
            sum += (i + POWERS_OF_TWO[16]) & 0x3f;
            sum += (i + POWERS_OF_TWO[15]) & 0x3f;
            sum += (i + POWERS_OF_TWO[46]) & 0x3f;
            sum += (i + POWERS_OF_TWO[32]) & 0x3f;
            sum += (i + POWERS_OF_TWO[17]) & 0x3f;
            sum += (i + POWERS_OF_TWO[48]) & 0x3f;
            sum += (i + POWERS_OF_TWO[41]) & 0x3f;
            sum += (i + POWERS_OF_TWO[39]) & 0x3f;
            sum += (i + POWERS_OF_TWO[12]) & 0x3f;
            sum += (i + POWERS_OF_TWO[51]) & 0x3f;
            sum += (i + POWERS_OF_TWO[21]) & 0x3f;
            sum += (i + POWERS_OF_TWO[0]) & 0x3f;
            sum += (i + POWERS_OF_TWO[50]) & 0x3f;
            sum += (i + POWERS_OF_TWO[44]) & 0x3f;
            sum += (i + POWERS_OF_TWO[2]) & 0x3f;
            sum += (i + POWERS_OF_TWO[60]) & 0x3f;
            sum += (i + POWERS_OF_TWO[34]) & 0x3f;
            sum += (i + POWERS_OF_TWO[31]) & 0x3f;
            sum += (i + POWERS_OF_TWO[30]) & 0x3f;
            sum += (i + POWERS_OF_TWO[53]) & 0x3f;
            sum += (i + POWERS_OF_TWO[61]) & 0x3f;
            sum += (i + POWERS_OF_TWO[1]) & 0x3f;
            sum += (i + POWERS_OF_TWO[27]) & 0x3f;
            sum += (i + POWERS_OF_TWO[62]) & 0x3f;
            sum += (i + POWERS_OF_TWO[25]) & 0x3f;
            sum += (i + POWERS_OF_TWO[28]) & 0x3f;
            sum += (i + POWERS_OF_TWO[11]) & 0x3f;
            sum += (i + POWERS_OF_TWO[5]) & 0x3f;
            sum += (i + POWERS_OF_TWO[6]) & 0x3f;
            sum += (i + POWERS_OF_TWO[42]) & 0x3f;
            sum += (i + POWERS_OF_TWO[59]) & 0x3f;
            sum += (i + POWERS_OF_TWO[47]) & 0x3f;
        }

        return sum;
    }

    @Benchmark
    public long testShiftUnroll() {
        long sum = 0;

        for (int i = 0; i < N; i++) {
            sum += 1L << (i + 35) & 0x3f;
            sum += 1L << (i + 52) & 0x3f;
            sum += 1L << (i + 55) & 0x3f;
            sum += 1L << (i + 57) & 0x3f;
            sum += 1L << (i + 38) & 0x3f;
            sum += 1L << (i + 13) & 0x3f;
            sum += 1L << (i + 36) & 0x3f;
            sum += 1L << (i + 19) & 0x3f;
            sum += 1L << (i + 7) & 0x3f;
            sum += 1L << (i + 48) & 0x3f;
            sum += 1L << (i + 8) & 0x3f;
            sum += 1L << (i + 0) & 0x3f;
            sum += 1L << (i + 45) & 0x3f;
            sum += 1L << (i + 2) & 0x3f;
            sum += 1L << (i + 14) & 0x3f;
            sum += 1L << (i + 44) & 0x3f;
            sum += 1L << (i + 31) & 0x3f;
            sum += 1L << (i + 6) & 0x3f;
            sum += 1L << (i + 25) & 0x3f;
            sum += 1L << (i + 18) & 0x3f;
            sum += 1L << (i + 34) & 0x3f;
            sum += 1L << (i + 41) & 0x3f;
            sum += 1L << (i + 37) & 0x3f;
            sum += 1L << (i + 32) & 0x3f;
            sum += 1L << (i + 1) & 0x3f;
            sum += 1L << (i + 53) & 0x3f;
            sum += 1L << (i + 9) & 0x3f;
            sum += 1L << (i + 16) & 0x3f;
            sum += 1L << (i + 62) & 0x3f;
            sum += 1L << (i + 4) & 0x3f;
            sum += 1L << (i + 12) & 0x3f;
            sum += 1L << (i + 46) & 0x3f;
            sum += 1L << (i + 17) & 0x3f;
            sum += 1L << (i + 29) & 0x3f;
            sum += 1L << (i + 63) & 0x3f;
            sum += 1L << (i + 51) & 0x3f;
            sum += 1L << (i + 21) & 0x3f;
            sum += 1L << (i + 24) & 0x3f;
            sum += 1L << (i + 49) & 0x3f;
            sum += 1L << (i + 40) & 0x3f;
            sum += 1L << (i + 58) & 0x3f;
            sum += 1L << (i + 59) & 0x3f;
            sum += 1L << (i + 33) & 0x3f;
            sum += 1L << (i + 61) & 0x3f;
            sum += 1L << (i + 56) & 0x3f;
            sum += 1L << (i + 42) & 0x3f;
            sum += 1L << (i + 5) & 0x3f;
            sum += 1L << (i + 23) & 0x3f;
            sum += 1L << (i + 22) & 0x3f;
            sum += 1L << (i + 43) & 0x3f;
            sum += 1L << (i + 60) & 0x3f;
            sum += 1L << (i + 15) & 0x3f;
            sum += 1L << (i + 11) & 0x3f;
            sum += 1L << (i + 27) & 0x3f;
            sum += 1L << (i + 30) & 0x3f;
            sum += 1L << (i + 54) & 0x3f;
            sum += 1L << (i + 10) & 0x3f;
            sum += 1L << (i + 3) & 0x3f;
            sum += 1L << (i + 50) & 0x3f;
            sum += 1L << (i + 28) & 0x3f;
            sum += 1L << (i + 47) & 0x3f;
            sum += 1L << (i + 20) & 0x3f;
            sum += 1L << (i + 26) & 0x3f;
            sum += 1L << (i + 39) & 0x3f;
        }

        return sum;
    }

    public static void main(final String[] args) throws IOException, RunnerException {
        Main.main(args);
    }
}

Результаты

# Run complete. Total time: 00:02:07

Benchmark                        Mode  Cnt   Score    Error  Units
LookupVsShift.testLookupUnroll  thrpt    3  23,072 ±  7,674  ops/s
LookupVsShift.testShiftUnroll   thrpt    3  20,834 ± 16,676  ops/s

Результат

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

...