Ответ 1
Я переписал этот ответ, поскольку я сначала суммировал все байты, это, однако, неверно, поскольку Java имеет подписанные байты, поэтому мне нужно или. Кроме того, я изменил разминку JVM, чтобы быть прав.
Лучше всего просто просто перебрать все значения.
Я предполагаю, что у вас есть три основных варианта:
- Или все элементы и проверьте сумму.
- Развертывание без связи.
- Выполняйте сравнения с веткой.
Я не знаю, насколько хороша производительность добавления байтов с использованием Java (низкая производительность), я знаю, что Java использует (низкоуровневые) ветки, если вы даете разветвленные сравнения.
Поэтому я ожидаю следующего:
byte[] array = new byte[4096];
for (byte b : array) {
if (b != 0) {
return false;
}
}
- Относительно медленное сравнение в первых нескольких итерациях, когда предсказатель ветвления все еще посеян.
- Очень быстрое сравнение ветвей из-за предсказания ветвления, так как каждое значение должно быть равно нулю.
Если бы это привело бы к ненулевому значению, то предиктор ветвления завершится неудачей, что приведет к замедлению сравнения, но тогда вы также находитесь в конце вашего вычисления, так как вы хотите возвратить false в любом случае. Я думаю, что стоимость одного неудачного прогноза ветвления на порядок меньше, чем стоимость продолжения итерации по массиву.
Кроме того, я считаю, что for (byte b : array)
должен быть разрешен, поскольку он должен быть скомпилирован непосредственно в итерации с индексированным массивом, насколько я знаю, нет такой вещи, как PrimitiveArrayIterator
, которая вызовет некоторые дополнительные вызовы методов (как итерация список), пока код не встанет в очередь.
Обновление
Я написал свои собственные тесты, которые дают некоторые интересные результаты... К сожалению, я не мог использовать ни один из существующих тестовых инструментов, так как их довольно сложно правильно установить.
Я также решил объединить варианты 1 и 2, так как я думаю, что они на самом деле те же, что и с ветвящимся вы обычно или все (минус условие), а затем проверяете окончательный результат. И здесь условие x > 0
, и, следовательно, a или нуль предположительно является noop.
Код:
public class Benchmark {
private void start() {
//setup byte arrays
List<byte[]> arrays = createByteArrays(700_000);
//warmup and benchmark repeated
arrays.forEach(this::byteArrayCheck12);
benchmark(arrays, this::byteArrayCheck12, "byteArrayCheck12");
arrays.forEach(this::byteArrayCheck3);
benchmark(arrays, this::byteArrayCheck3, "byteArrayCheck3");
arrays.forEach(this::byteArrayCheck4);
benchmark(arrays, this::byteArrayCheck4, "byteArrayCheck4");
arrays.forEach(this::byteArrayCheck5);
benchmark(arrays, this::byteArrayCheck5, "byteArrayCheck5");
}
private void benchmark(final List<byte[]> arrays, final Consumer<byte[]> method, final String name) {
long start = System.nanoTime();
arrays.forEach(method);
long end = System.nanoTime();
double nanosecondsPerIteration = (end - start) * 1d / arrays.size();
System.out.println("Benchmark: " + name + " / iterations: " + arrays.size() + " / time per iteration: " + nanosecondsPerIteration + "ns");
}
private List<byte[]> createByteArrays(final int amount) {
Random random = new Random();
List<byte[]> resultList = new ArrayList<>();
for (int i = 0; i < amount; i++) {
byte[] byteArray = new byte[4096];
byteArray[random.nextInt(4096)] = 1;
resultList.add(byteArray);
}
return resultList;
}
private boolean byteArrayCheck12(final byte[] array) {
int sum = 0;
for (byte b : array) {
sum |= b;
}
return (sum == 0);
}
private boolean byteArrayCheck3(final byte[] array) {
for (byte b : array) {
if (b != 0) {
return false;
}
}
return true;
}
private boolean byteArrayCheck4(final byte[] array) {
return (IntStream.range(0, array.length).map(i -> array[i]).reduce(0, (a, b) -> a | b) != 0);
}
private boolean byteArrayCheck5(final byte[] array) {
return IntStream.range(0, array.length).map(i -> array[i]).anyMatch(i -> i != 0);
}
public static void main(String[] args) {
new Benchmark().start();
}
}
Удивительные результаты:
Контрольный показатель: byteArrayCheck12/итерации: 700000/время на итерацию: 50.18817142857143ns
Контрольный показатель: byteArrayCheck3/итерации: 700000/время на итерацию: 767.7371985714286ns
Контрольный показатель: byteArrayCheck4/итерации: 700000/время на итерацию: 21145.03219857143ns
Тест: byteArrayCheck5/итерации: 700000/время на итерацию: 10376.119144285714ns
Это показывает, что оправа целая серия быстрее, чем предсказатель ветвления, что довольно удивительно, поэтому я предполагаю, что выполняется небольшая оптимизация.
В качестве дополнительных я включил варианты потока, которые я не ожидал, что так быстро.
Отладка на тактовой частоте Intel i7-3770, 16 ГБ с частотой 1600 МГц.
Итак, я думаю, что окончательный ответ: это зависит. Это зависит от того, сколько раз вы будете последовательно проверять массив. Решение "byteArrayCheck3" всегда находится на уровне 700 ~ 800 нс.
Последующее обновление
Вещи действительно занимают еще один интересный подход, оказывается, что JIT оптимизирует почти все расчеты, из-за того, что результирующие переменные вообще не используются.
Таким образом, у меня есть следующий новый метод benchmark
:
private void benchmark(final List<byte[]> arrays, final Predicate<byte[]> method, final String name) {
long start = System.nanoTime();
boolean someUnrelatedResult = false;
for (byte[] array : arrays) {
someUnrelatedResult |= method.test(array);
}
long end = System.nanoTime();
double nanosecondsPerIteration = (end - start) * 1d / arrays.size();
System.out.println("Result: " + someUnrelatedResult);
System.out.println("Benchmark: " + name + " / iterations: " + arrays.size() + " / time per iteration: " + nanosecondsPerIteration + "ns");
}
Это гарантирует, что результат тестов не может быть оптимизирован, поэтому основная проблема заключалась в том, что метод byteArrayCheck12
был недействительным, поскольку он заметил, что (sum == 0)
не использовался, следовательно, он оптимизировал весь метод.
Таким образом, мы получаем следующий новый результат (опускаем результат для ясности):
Бенчмарк: byteArrayCheck12/итерации: 700000/время на итерацию: 1370.6987942857143ns
Контрольный показатель: byteArrayCheck3/итерации: 700000/время на итерацию: 736.1096242857143ns
Контрольный показатель: byteArrayCheck4/итерации: 700000/время на итерацию: 20671.230327142857ns
Тест: byteArrayCheck5/итерации: 700000/время на итерацию: 9845.388841428572ns
Следовательно, мы думаем, что мы можем, наконец, заключить, что выигрывает предсказание отрасли. Это может также произойти из-за ранних результатов, так как в среднем байт-нарушение будет находиться в середине массива байтов, поэтому пришло время для другого метода, который не возвращается раньше:
private boolean byteArrayCheck3b(final byte[] array) {
int hits = 0;
for (byte b : array) {
if (b != 0) {
hits++;
}
}
return (hits == 0);
}
Таким образом, мы по-прежнему извлекаем выгоду из предсказания ветвей, однако мы не можем вернуться раньше.
Это, в свою очередь, снова дает нам интересные результаты!
Бенчмарк: byteArrayCheck12/итерации: 700000/время на итерацию: 1327.2817714285713ns
Контрольный показатель: byteArrayCheck3/итерации: 700000/время на итерацию: 753.31376ns
Контрольный показатель: byteArrayCheck3b/итерации: 700000/время на итерацию: 1506.6772842857142ns
Контрольный показатель: byteArrayCheck4/итерации: 700000/время на итерацию: 21655.950115714284ns
Benchmark: byteArrayCheck5/итерации: 700000/время на итерацию: 10608.70917857143ns
Я думаю, мы можем, наконец, заключить, что самым быстрым способом является использование как раннего возвращения, так и предсказания ветвлений, за которым следует орринга, а затем чисто предсказание ветвления. Я подозреваю, что все эти операции сильно оптимизированы в собственном коде.
Обновить, некоторый дополнительный бенчмаркинг с использованием массивов long и int.
После просмотра предложений по использованию long[]
и int[]
я решил, что стоит исследовать. Однако эти попытки могут не полностью соответствовать исходным ответам, тем не менее, могут быть интересными.
Во-первых, я изменил метод benchmark
на использование дженериков:
private <T> void benchmark(final List<T> arrays, final Predicate<T> method, final String name) {
long start = System.nanoTime();
boolean someUnrelatedResult = false;
for (T array : arrays) {
someUnrelatedResult |= method.test(array);
}
long end = System.nanoTime();
double nanosecondsPerIteration = (end - start) * 1d / arrays.size();
System.out.println("Result: " + someUnrelatedResult);
System.out.println("Benchmark: " + name + " / iterations: " + arrays.size() + " / time per iteration: " + nanosecondsPerIteration + "ns");
}
Затем я выполнил преобразования от byte[]
до long[]
и int[]
соответственно до тестов, также необходимо установить максимальный размер кучи до 10 ГБ.
List<long[]> longArrays = arrays.stream().map(byteArray -> {
long[] longArray = new long[4096 / 8];
ByteBuffer.wrap(byteArray).asLongBuffer().get(longArray);
return longArray;
}).collect(Collectors.toList());
longArrays.forEach(this::byteArrayCheck8);
benchmark(longArrays, this::byteArrayCheck8, "byteArrayCheck8");
List<int[]> intArrays = arrays.stream().map(byteArray -> {
int[] intArray = new int[4096 / 4];
ByteBuffer.wrap(byteArray).asIntBuffer().get(intArray);
return intArray;
}).collect(Collectors.toList());
intArrays.forEach(this::byteArrayCheck9);
benchmark(intArrays, this::byteArrayCheck9, "byteArrayCheck9");
private boolean byteArrayCheck8(final long[] array) {
for (long l : array) {
if (l != 0) {
return false;
}
}
return true;
}
private boolean byteArrayCheck9(final int[] array) {
for (int i : array) {
if (i != 0) {
return false;
}
}
return true;
}
Это дало следующие результаты:
Контрольная точка: byteArrayCheck8/итерации: 700000/время на итерацию: 259.8157614285714ns
Контрольный показатель: byteArrayCheck9/итерации: 700000/время на итерацию: 266.38013714285717ns
Этот путь, возможно, стоит изучить, если возможно получить байты в таком формате. Однако при выполнении преобразований внутри эталонного метода время составляло около 2000 наносекунд на итерацию, поэтому оно не стоит, когда вам нужно делать преобразования самостоятельно.