Почему (a * b! = 0) быстрее, чем (a! = 0 && b! = 0) в Java?

Я пишу код на 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:

Графический график результатов a AND b ненулевого

Мне любопытно узнать почему. Может ли кто-нибудь пролить свет? Это компилятор или на аппаратном уровне?

Изменить: Из любопытства ... теперь, когда я узнал о предсказании ветвлений, мне было интересно, что покажет аналоговое сравнение для ИЛИ b не равно нулю:

График a или 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, смешанный режим)


person Maljam    schedule 21.02.2016    source источник
comment
А что насчет if (!(a == 0 || b == 0))? Микробенчмарки общеизвестно ненадежны, это вряд ли реально измерить (~ 3% для меня звучит как предел погрешности).   -  person Elliott Frisch    schedule 21.02.2016
comment
Попробуйте сравнить a*b != 0 с !( a == 0 || b == 0). Если эти два результата намного ближе друг к другу, это ошибка оптимизации.   -  person Todd Knarr    schedule 21.02.2016
comment
Ветвление происходит медленно, если предсказанное ветвление неверно. a*b!=0 на одну ветку меньше   -  person Erwin Bolwidt    schedule 21.02.2016
comment
Я добавил! (A == 0 || b == 0) к анализу, как вы, ребята, предложили, и он точно такой же, как a! = 0 && b! = 0. Кажется, это действительно зависит от количества компараторов. , и что арифметические операторы и намного быстрее   -  person Maljam    schedule 21.02.2016
comment
Я немного удивлен, что умножение происходит быстрее, чем сравнение, даже учитывая эффект конвейерной обработки, на который указал @StevenC. Было бы интересно посмотреть, что делает JIT-компилятор путем оптимизации двух форм. Очень просто оптимизировать с помощью глазка a*b==0 до (a|b) == 0, тогда как оптимизация короткозамкнутого сравнения требует больше работы. Тем не менее, вы должны попробовать опцию a|b, так как это будет не более одного цикла (он может быть даже бесплатным, если отправлен с другой операцией), тогда как умножение обычно составляет от 2 до 4 циклов в современных x86.   -  person Gene    schedule 21.02.2016
comment
(1<<16) * (1<<16) == 0 но оба отличны от нуля.   -  person CodesInChaos    schedule 21.02.2016
comment
Примерно 1 из 12 человек страдает дальтонизмом в той или иной форме. Графики, использующие только цвет для различения точек, могут быть трудными или невозможными для этих людей. Обычное решение - сделать точки разной формы. Тем не менее, это прекрасный график и отличный вопрос.   -  person Wayne Conrad    schedule 21.02.2016
comment
@Gene: Предложенная вами оптимизация недействительна. Даже игнорируя переполнение, a*b равно нулю, если единица из a и b равно нулю; a|b равно нулю, только если оба равны.   -  person hmakholm left over Monica    schedule 21.02.2016
comment
Поскольку проблема, вероятно, связана с предсказанием ветвления, убедитесь, что вы проводите тестирование с фактическими значениями из вашей программы в том порядке, в котором они появляются в программе. Выбранная / невыбранная последовательность для данного условного перехода контролирует, насколько хорошо работает прогнозирование ветвления. Правильно спрогнозированный переход - это быстрая операция.   -  person Patricia Shanahan    schedule 21.02.2016
comment
Вы видели этот ответ? Вы можете использовать JMH, чтобы написать микробенчмарк и получить низкоуровневую информацию, включая статистика прогнозирования ветвления ЦП.   -  person zloster    schedule 21.02.2016
comment
@CodesInChaos Именно. Конечно, таких примеров очень много. Например, 983040 * 6422528 == 0 (как 32-битное целое число), но ни один из множителей не равен нулю (как 32-битное целое число). Если вы видите его как шестнадцатеричный, с произвольной точностью мой пример - 0xF0000 * 0x620000 == 0x5BE00000000, но поскольку 32-битное целое число сохраняет только 8 младших шестнадцатеричных цифр, произведение равно 0x00000000 или просто ноль.   -  person Jeppe Stig Nielsen    schedule 21.02.2016
comment
Мне жаль. Мозговая судорога. Я имел в виду (a&b)!=0.   -  person Gene    schedule 21.02.2016
comment
Преждевременная оптимизация - корень всех зол. Прекратите делать бессмысленные микрооптимизации и вместо этого улучшите алгоритмы.   -  person user882813    schedule 21.02.2016
comment
@Gene: Это тоже не работает; взять напр. 1&2.   -  person Aleksi Torhamo    schedule 21.02.2016
comment
@ user882813 Почему? Если бы вы могли сократить время выполнения вашей программы более чем на 25% с помощью этого трюка, разве вы не сделали бы это?   -  person user253751    schedule 22.02.2016
comment
@immibis, потому что Интернет и коллеги снова и снова говорили ему, что поиск и обнаружение таких оптимизаций называется преждевременной оптимизацией, а преждевременная оптимизация - это плохо. Вот почему. Кто-то где-то это сказал, так что это универсально верно. Боже, разве ты не получил записку?   -  person    schedule 22.02.2016
comment
Я обычно открываю пост. Почему использование itneger 1 не является постоянным вычислением, результатом которого является 1 faste? ....   -  person dhein    schedule 22.02.2016
comment
Чтобы проверить, является ли это компилятором или оборудованием, вы можете отключить оптимизацию компилятора ...   -  person elyashiv    schedule 22.02.2016
comment
@TechnikEmpire На самом деле парней двое. Тот, который по определению кричит, что слишком рано проводить оптимизацию в процессе разработки - это плохо - этому человеку следует запретить дискуссии. Но есть и другой, хороший ученый: вызывает преждевременно реализованные оптимизации, не тратя сначала достаточно времени на проверку того, что они не влияют на фактическую логику. Не пренебрегайте его мудростью!   -  person Gimby    schedule 22.02.2016
comment
В некотором смысле это привычка использовать &&, а не &, когда результат тот же (никаких побочных эффектов не следует учитывать), что и является преждевременной оптимизацией. Мы склонны отдавать предпочтение && языкам в стиле C, потому что они имеют тенденцию быть быстрее (выполнять меньше работы), но иногда это преждевременно, когда оказывается, что добавленная ветвь стоит больше, чем пропущенная работа. (Честно говоря, также чаще бывает, что & просто неверно, чем && неверно, но предпочтение && восходит к тому времени в C, когда предсказание ветвления не было таким большим влиянием, а влияние скорости повлияло на привычки к более поздним языкам).   -  person Jon Hanna    schedule 22.02.2016
comment
Если неверное предсказание ветвления действительно является проблемой (как я подозреваю), тогда вы должны увидеть еще лучшую производительность от побитового оператора & без ветвления: if ((a != 0) & (b != 0)). Это не только позволит избежать проблемы переполнения, но и должно быть значительно быстрее, учитывая, насколько медленным является умножение. Это и деление - единственные операции, которые требуют нескольких циклов; для сравнения: бит-тиддлинг - это очень быстро.   -  person Cody Gray    schedule 23.02.2016
comment
Я так же удивлен, что побитовое и работает с логическими значениями, но Java - не мой обычный язык. Тем не менее, я подозреваю, что одно или оба моих предложения будут работать одинаково.   -  person Pagefault    schedule 23.02.2016
comment
Вы пишете, что a и b - переменные, но это не так, это более сложные выражения. Как отмечено в ответе @ Pagefault, это вполне может иметь значение.   -  person Carsten S    schedule 23.02.2016
comment
Похоже, большую часть моей карьеры в колледже .... Пишу код, который ... .... Ооооо, шиииины графики. Черт возьми, кого я обманываю, я все еще делаю это.   -  person Jack    schedule 24.02.2016
comment
@AleksiTorhamo Спасибо. Извините. Это был длинный день. Не думал ясно.   -  person Gene    schedule 24.02.2016
comment
@CodyGray Nitpicking: когда операнды для & являются логическими, это не побитовый, а логический оператор, см. JLS §15.22.   -  person siegi    schedule 24.02.2016
comment
@siegi Blargh! Конечно же. Это то, что я получаю как специалист по C ++, комментируя вопрос, который всплывает в разделе горячих вопросов. :-) Фактически, это может объяснить, почему обновленный тест показывает, что он работает не так хорошо, как я ожидал. Компилятор Java или среда выполнения могут оказаться не в состоянии оптимизировать это так эффективно, как если бы это была побитовая операция. Возможно, он все еще компилирует его в ветку.   -  person Cody Gray    schedule 24.02.2016
comment
@HenningMakholm: Насколько я читал, он предлагает (a|b) != 0 в качестве оптимизации для a != 0 || b != 0 и (a*b) != 0 для a != 0 && b != 0 - первое нормально, второе рискует переполниться числами ›= 2¹⁶.   -  person PJTraill    schedule 24.02.2016
comment
@siegi: Разумеется, JLS 15.22 не определяет ленивую оценку b в a&b, которая будет определять, будет ли использоваться ветвь (и возникнут ли побочные эффекты). Но если вы критикуете только «побитовое», то вы правы - если только мы не принимаем, что логическое значение имеет один бит!   -  person PJTraill    schedule 24.02.2016
comment
@immibis Просто потому, что вы не получите повышения на 25%, просто заменив медленную примитивную операцию быстрой (и, кстати, неправильной, как в этом вопросе). Ушли в прошлое добрые времена, когда можно резко увеличить скорость вычислений, просто заменив деление на 2 побитовым сдвигом. Навсегда. В настоящее время для повышения производительности вы должны, нет, не должны, вы должны включить свой мозг и улучшить алгоритмы. Или, по крайней мере, выбрать правильный. К сожалению, в нашем мире больше нет волшебных единорогов и фей. Вместо этого у нас есть терабайты хитро оптимизированного некачественного кода.   -  person user882813    schedule 01.03.2016
comment
@ user882813, вы не получите прироста на 25%, просто заменив медленную примитивную операцию на быструю. Тогда почему же спрашивающий показал именно такой результат?   -  person user253751    schedule 01.03.2016
comment
Что, если a и b - большие числа?   -  person J J    schedule 17.07.2017
comment
Я просто хочу прокомментировать «преждевременная оптимизация - корень всех зол». Во-первых, нет никаких доказательств того, что здесь преждевременно. Во-вторых, это частичная цитата. На самом деле Кнут сказал: «Мы должны забыть о небольшой эффективности, скажем, примерно в 97% случаев: преждевременная оптимизация - это корень всех зол. Тем не менее, мы не должны упускать наши возможности в этих критических 3% ». И многое другое в том же духе. К сожалению, это стало одной из самых неверных цитат в истории вычислительной техники.   -  person user207421    schedule 29.02.2020


Ответы (5)


Я игнорирую проблему того, что ваш сравнительный анализ может быть некорректным, и принимаю результат за чистую монету.

Это компилятор или на аппаратном уровне?

Последнее, я думаю:

  if (a != 0 && b != 0)

будет компилироваться до 2 загрузок памяти и двух условных ветвей

  if (a * b != 0)

будет компилироваться в 2 загрузки памяти, умножение и одну условную ветвь.

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

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

(Примечание: приведенное выше объяснение слишком упрощено. Для более точного объяснения вам необходимо изучить литературу, предоставленную производителем ЦП для программистов на ассемблере и составителей компиляторов. Страница Википедии на Branch Predictors - хороший фон.)


Однако есть одна вещь, о которой вам нужно быть осторожным при этой оптимизации. Есть ли значения, при которых a * b != 0 даст неправильный ответ? Рассмотрим случаи, когда вычисление продукта приводит к целочисленному переполнению.


ОБНОВЛЕНИЕ

Ваши графики подтверждают то, что я сказал.

  • Также есть эффект «предсказания ветвления» в случае условного ветвления a * b != 0, и это проявляется на графиках.

  • Если вы спроецируете кривые за пределы 0,9 на ось X, это будет выглядеть так: 1) они встретятся примерно в 1,0 и 2) точка встречи будет примерно при том же значении Y, что и для X = 0,0.


ОБНОВЛЕНИЕ 2

Я не понимаю, почему кривые разные для случаев a + b != 0 и a | b != 0. В логике предсказателей ветвления может быть что-то умное. Или это могло указывать на что-то еще.

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

Однако у них обоих есть то преимущество, что они работают для всех неотрицательных значений a и b.

person Stephen C    schedule 21.02.2016
comment
Спасибо за ответ, довольно интересно. И чтобы решить вашу проблему сравнительного анализа, я просто повторил произвольную переменную типа int (arb ++). Я пропустил его, потому что подумал, что это не имеет значения, если я использую тот же самый. - person Maljam; 21.02.2016
comment
@StephenC Пожалуйста, поправьте меня, если я ошибаюсь. Для (a != 0 && b != 0) вам нужно будет загрузить a, выполнить beqz с 0, сохранить значение; то же самое для b. Итак, у нас есть 2 lw, 2 sw и 2 ответвления. Для (a * b != 0) у вас есть два lw для a, b; один sw для хранения a*b, а затем beqz для сравнения с 0. Итак, у нас есть 2 lw, 1 sw и 1 ветка. Как вы думаете, это рассуждение правильное? Кроме того, во втором случае есть только одна доступная ветвь, поэтому предсказатель ветвления всегда будет иметь вероятность, стремящуюся к 1. - person Debosmit Ray; 21.02.2016
comment
@DebosmitRay - 1) ПО не должно быть. Промежуточные результаты будут храниться в журнале. 2) Во втором случае есть две доступные ветви: одна для выполнения некоторого кода, а другая для перехода к следующему оператору после if. - person Stephen C; 21.02.2016
comment
@StephenC, вы правы, запутавшись насчет a + b и a | b, потому что кривые одинаковы, я думаю, что цвета действительно близки. Приносим свои извинения дальтоникам! - person Maljam; 21.02.2016
comment
@Maljam - я имел в виду - я озадачен тем, что они отличаются от случая a * b != 0. Я не ожидал этого. Может быть, это ключ к разгадке нативного кода, создаваемого JIT-компилятором. Или, может быть, мы ›› ‹› видим артефакт бенчмаркинга. - person Stephen C; 21.02.2016
comment
@StephenC результат, вероятно, другой, потому что a*b и a+b разные. предсказание ветвления не может работать с ними одинаково. - person njzk2; 21.02.2016
comment
@ njzk2 с точки зрения вероятности эти случаи должны быть симметричными по оси на 50% (вероятность нуля для a&b и a|b). Они есть, но не идеально, вот в чем загадка. - person Antonín Lejsek; 21.02.2016
comment
@StephenC Причина, по которой тесты a*b != 0 и a+b != 0 различаются, заключается в том, что a+b != 0 совсем не эквивалентен и никогда не должен был тестироваться. Например, с a = 1, b = 0 первое выражение оценивается как ложное, а второе - как истинное. Умножение действует как оператор и, тогда как добавление действует как оператор или. - person JS1; 21.02.2016
comment
@ AntonínLejsek Я думаю, что вероятности будут разными. Если у вас n нули, то вероятность того, что и a, и b равны нулю, увеличивается с n. В AND операции с более высоким n увеличивается вероятность того, что один из них не равен нулю, и условие выполняется. Для операции OR это противоположно (вероятность того, что любой из них равен нулю, увеличивается с n). Это основано на математической перспективе. Я не уверен, так ли работает оборудование. - person WYSIWYG; 23.02.2016
comment
В чем может быть разница в случаях 1 и 3 на первом графике? - person Amber Beriwal; 08.03.2016
comment
Одно дело - то, что компилятор испускает на уровне байтового кода, другое - то, что на самом деле делает машинный код, сгенерированный HotSpot. - person Thorbjørn Ravn Andersen; 21.02.2019
comment
Ага. См. Первые несколько строк моего ответа. - person Stephen C; 21.02.2019

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

  • (a|b)!=0 и (a+b)!=0 проверяют, если любое значение не равно нулю, тогда как a != 0 && b != 0 и (a*b)!=0 проверяют, если оба отличны от нуля. Таким образом, вы сравниваете время не только арифметических операций: если условие выполняется чаще, это вызывает большее количество выполнений тела if, что также занимает больше времени.

  • (a+b)!=0 будет делать неправильные вещи для положительных и отрицательных значений, которые в сумме равны нулю, поэтому вы не можете использовать его в общем случае, даже если он работает здесь.

  • Точно так же (a*b)!=0 поступит неправильно с переполненными значениями. (Случайный пример: 196608 * 327680 равно 0, потому что истинный результат делится на 2 32, поэтому его младшие 32 бита равны 0, и эти биты - все, что вы получаете, если это операция int.)

  • ВМ оптимизирует выражение во время первых нескольких запусков внешнего (fraction) цикла, когда fraction равно 0, когда ветви почти никогда не выполняются. Оптимизатор может делать разные вещи, если вы начнете fraction с 0,5.

  • Если виртуальная машина не может устранить здесь некоторые проверки границ массива, в выражении есть еще четыре ветви только из-за проверок границ, и это усложняет попытку выяснить, что происходит на низком уровне. Вы можете получить разные результаты, если разделите двумерный массив на два плоских массива, изменив nums[0][i] и nums[1][i] на nums0[i] и nums1[i].

  • Предикторы ветвления ЦП обнаруживают короткие шаблоны в данных или запуски всех ветвей, которые выполняются или не выполняются. Ваши случайно сгенерированные контрольные данные являются худшими -сценарий для предсказателя ветвления. Если у реальных данных есть предсказуемая закономерность или у них есть длинные серии нулевых и ненулевых значений, переходы могут стоить намного меньше.

  • Конкретный код, который выполняется после того, как условие выполнено, может повлиять на производительность оценки самого условия, потому что он влияет на такие вещи, как то, можно ли развернуть цикл, какие регистры ЦП доступны, и требуется ли какое-либо из полученных nums значений. для повторного использования после оценки состояния. Простое увеличение счетчика в тесте не является идеальным заменителем того, что может делать настоящий код.

  • System.currentTimeMillis() в большинстве систем не более точен, чем +/- 10 мс. System.nanoTime() обычно более точен.

Есть много неопределенностей, и всегда трудно сказать что-либо определенное об этих видах микрооптимизации, потому что трюк, который быстрее на одной виртуальной машине или процессоре, может быть медленнее на другом. Если вы используете 32-битную JVM HotSpot, а не 64-битную версию, имейте в виду, что она бывает двух видов: с «клиентской» виртуальной машиной, имеющей другие (более слабые) оптимизации по сравнению с «серверной» виртуальной машиной.

Если вы можете дизассемблировать машинный код, сгенерированный виртуальной машиной, сделайте это, а не пытаюсь угадать, что он делает!

person Boann    schedule 21.02.2016

Ответы здесь хорошие, хотя у меня была идея, которая могла бы улучшить ситуацию.

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

bool aNotZero = (nums[0][i] != 0);
bool bNotZero = (nums[1][i] != 0);
if (aNotZero && bNotZero) { /* Some code */ }

Также может сработать

int a = nums[0][i];
int b = nums[1][i];
if (a != 0 && b != 0) { /* Some code */ }

Причина в том, что по правилам короткого замыкания, если первое логическое значение ложно, второе не должно оцениваться. Он должен выполнить дополнительную ветвь, чтобы избежать оценки nums[1][i], если nums[0][i] было ложным. Теперь вам может быть все равно, что nums[1][i] оценивается, но компилятор не может быть уверен, что он не выдаст ссылку за пределы допустимого диапазона или нулевую ссылку, когда вы это сделаете. Уменьшая блок if до простых булевых значений, компилятор может быть достаточно умен, чтобы понять, что оценка второго логического значения без необходимости не будет иметь отрицательных побочных эффектов.

person Pagefault    schedule 22.02.2016
comment
Проголосовали за, хотя у меня такое чувство, что это не вполне ответа на вопрос. - person Pierre Arlaud; 22.02.2016
comment
Это способ создать ветвь без изменения логики, не связанной с ветвлением (если способ, которым вы получили a и b, имел побочные эффекты, вы бы сохранили их). У вас все еще есть &&, так что у вас все еще есть ветка. - person Jon Hanna; 22.02.2016

Когда мы берем умножение, даже если одно число равно 0, тогда произведение равно 0. При записи

    (a*b != 0)

Он оценивает результат продукта, тем самым устраняя первые несколько вхождений итерации, начиная с 0. В результате сравнения меньше, чем при условии

   (a != 0 && b != 0)

Где каждый элемент сравнивается с 0 и оценивается. Следовательно, требуется меньше времени. Но я считаю, что второе условие может дать вам более точное решение.

person Sanket Gupte    schedule 21.02.2016
comment
Во втором выражении, если a равно нулю, тогда b не нужно оценивать, поскольку все выражение уже ложно. Итак, сравнивается каждый элемент неверно. - person Kuba Wyrostek; 22.02.2016

Вы используете рандомизированные входные данные, что делает ветви непредсказуемыми. На практике ветвления часто (~ 90%) предсказуемы, поэтому в реальном коде ветвящийся код, вероятно, будет быстрее.

Тем не менее. Я не понимаю, как a*b != 0 может быть быстрее (a|b) != 0. Обычно целочисленное умножение дороже, чем побитовое ИЛИ. Но такие вещи иногда становятся странными. См., Например, пример «Пример 7: Аппаратные сложности» из Галереи эффектов кэша процессора.

person StackedCrooked    schedule 24.02.2016
comment
& не является побитовым ИЛИ, но (в данном случае) логическим И, потому что оба операнда являются логическими, а не | ;-) - person siegi; 24.02.2016
comment
@siegi TIL Java '&' на самом деле является логическим И без короткого замыкания. - person StackedCrooked; 24.02.2016