Я пишу некоторый код на Java, где в какой-то момент поток программы определяется тем, являются ли две переменные int, "a" и "b", ненулевыми (примечание: a и b никогда не бывают отрицательными, и никогда в пределах диапазона целочисленного переполнения).
Я могу оценить это с
if (a != 0 && b != 0) { /* Some code */ }
Или в качестве альтернативы
if (a*b != 0) { /* Some code */ }
Поскольку я ожидаю, что этот фрагмент кода будет выполняться миллионы раз за цикл, мне было интересно, какой из них будет быстрее. Я провел эксперимент, сравнивая их с огромным случайно сгенерированным массивом, и мне также было любопытно посмотреть, как разреженность массива (доля данных = 0) повлияет на результаты:
long time;
final int len = 50000000;
int arbitrary = 0;
int[][] nums = new int[2][len];
for (double fraction = 0 ; fraction <= 0.9 ; fraction += 0.0078125) {
for(int i = 0 ; i < 2 ; i++) {
for(int j = 0 ; j < len ; j++) {
double random = Math.random();
if(random < fraction) nums[i][j] = 0;
else nums[i][j] = (int) (random*15 + 1);
}
}
time = System.currentTimeMillis();
for(int i = 0 ; i < len ; i++) {
if( /*insert nums[0][i]*nums[1][i]!=0 or nums[0][i]!=0 && nums[1][i]!=0*/ ) arbitrary++;
}
System.out.println(System.currentTimeMillis() - time);
}
И результаты показывают, что если вы ожидаете, что «a» или «b» будут равны 0 более чем в 3% случаев, a*b != 0
это быстрее, чем a!=0 && b!=0
:
Мне любопытно узнать почему. Может ли кто-нибудь пролить свет? Это компилятор или аппаратный уровень?
Редактировать: из любопытства ... теперь, когда я узнал о предсказании ветвлений, мне было интересно, что аналоговое сравнение покажет для ИЛИ b ненулевое:
Мы видим тот же эффект предсказания ветвлений, как и ожидалось, интересно, что график несколько перевернут вдоль оси X.
Обновить
1- Я добавил !(a==0 || b==0)
в анализ, чтобы увидеть, что происходит.
2- я включил a != 0 || b != 0
, (a+b) != 0
и (a|b) != 0
из любопытства, после изучения предсказания ветвлений. Но они не являются логически эквивалентными другим выражениям, потому что только OR b должно быть ненулевым, чтобы возвращать true, поэтому их не нужно сравнивать для эффективности обработки.
3. Я также добавил фактический тест, который я использовал для анализа, который просто повторяет произвольную переменную типа int.
4- Некоторые люди предлагали включить, a != 0 & b != 0
в отличие от a != 0 && b != 0
, с прогнозом, что он будет вести себя более тесно, a*b != 0
потому что мы удалим эффект прогнозирования ветвления. Я не знал, что &
можно использовать с булевыми переменными, я думал, что он используется только для двоичных операций с целыми числами.
Примечание: в контексте, который я рассматривал, переполнение int не является проблемой, но это, безусловно, важное соображение в общем контексте.
Процессор: Intel Core i7-3610QM @ 2,3 ГГц
Версия Java: 1.8.0_45
Java (TM) SE Runtime Environment (сборка 1.8.0_45-b14)
Java HotSpot (TM) 64-битная виртуальная машина сервера (сборка 25.45-b02, смешанный режим)
a != 0 & b != 0
.
a*b!=0
имеет на одну ветвь меньше
(1<<16) * (1<<16) == 0
все же оба отличаются от нуля.
a*b
равен нулю, если один из a
и b
равен нулю; a|b
ноль, только если оба.
if (!(a == 0 || b == 0))
? Общеизвестно, что микробенчмарки ненадежны, вряд ли это можно измерить (для меня ~ 3% - это предел погрешности).