Я пишу код на 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 должно быть ненулевым, чтобы вернуть истину, поэтому они не предназначены для сравнения для эффективности обработки.
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 (сборка 1.8.0_45-b14)
64-разрядная серверная виртуальная машина Java HotSpot (TM) (сборка 25.45-b02, смешанный режим)
if (!(a == 0 || b == 0))
? Микробенчмарки общеизвестно ненадежны, это вряд ли реально измерить (~ 3% для меня звучит как предел погрешности). - person Elliott Frisch   schedule 21.02.2016a*b != 0
с!( a == 0 || b == 0)
. Если эти два результата намного ближе друг к другу, это ошибка оптимизации. - person Todd Knarr   schedule 21.02.2016a*b!=0
на одну ветку меньше - person Erwin Bolwidt   schedule 21.02.2016a*b==0
до(a|b) == 0
, тогда как оптимизация короткозамкнутого сравнения требует больше работы. Тем не менее, вы должны попробовать опциюa|b
, так как это будет не более одного цикла (он может быть даже бесплатным, если отправлен с другой операцией), тогда как умножение обычно составляет от 2 до 4 циклов в современных x86. - person Gene   schedule 21.02.2016(1<<16) * (1<<16) == 0
но оба отличны от нуля. - person CodesInChaos   schedule 21.02.2016a*b
равно нулю, если единица изa
иb
равно нулю;a|b
равно нулю, только если оба равны. - person hmakholm left over Monica   schedule 21.02.2016983040 * 6422528 == 0
(как 32-битное целое число), но ни один из множителей не равен нулю (как 32-битное целое число). Если вы видите его как шестнадцатеричный, с произвольной точностью мой пример -0xF0000 * 0x620000 == 0x5BE00000000
, но поскольку 32-битное целое число сохраняет только 8 младших шестнадцатеричных цифр, произведение равно0x00000000
или просто ноль. - person Jeppe Stig Nielsen   schedule 21.02.2016(a&b)!=0
. - person Gene   schedule 21.02.20161&2
. - person Aleksi Torhamo   schedule 21.02.2016&&
, а не&
, когда результат тот же (никаких побочных эффектов не следует учитывать), что и является преждевременной оптимизацией. Мы склонны отдавать предпочтение&&
языкам в стиле C, потому что они имеют тенденцию быть быстрее (выполнять меньше работы), но иногда это преждевременно, когда оказывается, что добавленная ветвь стоит больше, чем пропущенная работа. (Честно говоря, также чаще бывает, что&
просто неверно, чем&&
неверно, но предпочтение&&
восходит к тому времени в C, когда предсказание ветвления не было таким большим влиянием, а влияние скорости повлияло на привычки к более поздним языкам). - person Jon Hanna   schedule 22.02.2016&
без ветвления:if ((a != 0) & (b != 0))
. Это не только позволит избежать проблемы переполнения, но и должно быть значительно быстрее, учитывая, насколько медленным является умножение. Это и деление - единственные операции, которые требуют нескольких циклов; для сравнения: бит-тиддлинг - это очень быстро. - person Cody Gray   schedule 23.02.2016&
являются логическими, это не побитовый, а логический оператор, см. JLS §15.22. - person siegi   schedule 24.02.2016(a|b) != 0
в качестве оптимизации дляa != 0 || b != 0
и(a*b) != 0
дляa != 0 && b != 0
- первое нормально, второе рискует переполниться числами ›= 2¹⁶. - person PJTraill   schedule 24.02.2016b
вa&b
, которая будет определять, будет ли использоваться ветвь (и возникнут ли побочные эффекты). Но если вы критикуете только «побитовое», то вы правы - если только мы не принимаем, что логическое значение имеет один бит! - person PJTraill   schedule 24.02.2016a
иb
- большие числа? - person J J   schedule 17.07.2017