В Java может & быть быстрее, чем &&?

В этом коде:

if (value >= x && value <= y) {

когда value >= x и value <= y являются такими же правдоподобными, как false, без определенного шаблона, использование оператора & выполняется быстрее, чем использование &&?

В частности, я думаю о том, как && лениво оценивает правое выражение (т.е. только если LHS является истинным), что подразумевает условное, тогда как в Java & в этом контексте гарантируется строгая оценка оба (булевых) подвыражения. Результат результата тот же самый.

Но пока оператор >= или <= будет использовать простую инструкцию сравнения, && должен включать ветвь, и эта ветвь восприимчива к ошибке предсказания ветвления - согласно этому очень известному вопросу: Почему быстрее обрабатывается отсортированный массив, чем несортированный массив?

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

Примечания:

  • Очевидно, что ответ на мой вопрос был бы "Нет", если бы код выглядел так: if(value >= x && verySlowFunction()). Я фокусируюсь на "достаточно простых" выражениях RHS.
  • там условная ветвь в любом случае (оператор if). Я не могу доказать себе, что это не имеет значения, и что альтернативные формулировки могут быть лучшими примерами, такими как boolean b = value >= x && value <= y;
  • все это попадает в мир ужасающих микрооптимизаций. Да, я знаю:-)... интересно хотя?

Обновление Просто чтобы объяснить, почему мне интересно: я смотрел на системы, о которых писал Мартин Томпсон на своем блоке "Механическая симпатия" , после того, как он пришел, и сделал беседу об Aeron. Одним из ключевых сообщений является то, что у нашего оборудования есть все это волшебное вещество в нем, и мы, разработчики программного обеспечения, трагически не в состоянии воспользоваться им. Не волнуйтесь, я не собираюсь идти s/& &/\ &/на весь мой код:-)... но на этом сайте есть ряд вопросов по улучшению прогнозирования ветвления путем удаления ветвей, и мне пришло в голову, что условные булевы операторы лежат в основе тестовых условий.

Конечно, @StephenC дает фантастический момент, что изгиб вашего кода в странные формы может сделать его менее легким для JITs, чтобы определить общие оптимизации - если не сейчас, то в будущем. И что упомянутый выше очень известный вопрос является особенным, поскольку он подталкивает сложность прогнозирования далеко за пределы практической оптимизации.

Я в значительной степени осознаю, что в большинстве (или почти всех) ситуациях && - самая ясная, простая, самая быстрая, лучшая вещь, хотя я очень благодарен людям, которые опубликовали ответы, демонстрирующие это! Мне действительно интересно узнать, есть ли вообще какие-либо случаи в любом случае, когда ответ на вопрос "Может ли & быть быстрее?" может быть Да...

Обновление 2: (Обращаясь к советам, что вопрос слишком широк. Я не хочу вносить существенные изменения в этот вопрос, потому что он может поставить под угрозу некоторые из приведенных ниже ответов, которые имеют исключительное качество!) Возможно, нужен пример в дикой природе; это из класса Guava LongMath (спасибо, спасибо за @maaartinus за его поиск):

public static boolean isPowerOfTwo(long x) {
    return x > 0 & (x & (x - 1)) == 0;
}

Посмотрите, что первый &? И если вы проверите ссылку, следующий метод называется lessThanBranchFree(...), который намекает, что мы находимся на территории отказа от ветвей - и Гуава действительно широко используется: каждый сохраненный цикл заставляет уровни моря заметно уменьшаться. Поэтому давайте ставим вопрос таким образом: это использование & (где && было бы более нормальным) реальную оптимизацию?

Ответ 1

Хорошо, так что вы хотите знать, как он ведет себя на более низком уровне... Давайте посмотрим на байт-код тогда!

EDIT: добавлен сгенерированный код сборки для AMD64 в конце. Посмотрите на интересные заметки.
EDIT 2 (re: OP "Update 2" ): добавлен код asm для Guava isPowerOfTwo method.

Источник Java

Я написал эти два быстрых метода:

public boolean AndSC(int x, int value, int y) {
    return value >= x && value <= y;
}

public boolean AndNonSC(int x, int value, int y) {
    return value >= x & value <= y;
}

Как вы можете видеть, они точно такие же, за исключением типа оператора AND.

Байт-код Java

И это сгенерированный байт-код:

  public AndSC(III)Z
   L0
    LINENUMBER 8 L0
    ILOAD 2
    ILOAD 1
    IF_ICMPLT L1
    ILOAD 2
    ILOAD 3
    IF_ICMPGT L1
   L2
    LINENUMBER 9 L2
    ICONST_1
    IRETURN
   L1
    LINENUMBER 11 L1
   FRAME SAME
    ICONST_0
    IRETURN
   L3
    LOCALVARIABLE this Ltest/lsoto/AndTest; L0 L3 0
    LOCALVARIABLE x I L0 L3 1
    LOCALVARIABLE value I L0 L3 2
    LOCALVARIABLE y I L0 L3 3
    MAXSTACK = 2
    MAXLOCALS = 4

  // access flags 0x1
  public AndNonSC(III)Z
   L0
    LINENUMBER 15 L0
    ILOAD 2
    ILOAD 1
    IF_ICMPLT L1
    ICONST_1
    GOTO L2
   L1
   FRAME SAME
    ICONST_0
   L2
   FRAME SAME1 I
    ILOAD 2
    ILOAD 3
    IF_ICMPGT L3
    ICONST_1
    GOTO L4
   L3
   FRAME SAME1 I
    ICONST_0
   L4
   FRAME FULL [test/lsoto/AndTest I I I] [I I]
    IAND
    IFEQ L5
   L6
    LINENUMBER 16 L6
    ICONST_1
    IRETURN
   L5
    LINENUMBER 18 L5
   FRAME SAME
    ICONST_0
    IRETURN
   L7
    LOCALVARIABLE this Ltest/lsoto/AndTest; L0 L7 0
    LOCALVARIABLE x I L0 L7 1
    LOCALVARIABLE value I L0 L7 2
    LOCALVARIABLE y I L0 L7 3
    MAXSTACK = 3
    MAXLOCALS = 4

Метод AndSC (&&) генерирует условные переходы два, как и ожидалось:

  • Он загружает value и x в стек и переходит на L1, если value ниже. Кроме того, он продолжает выполнять следующие строки.
  • Он загружает value и y в стек, а также переходит в L1, если value больше. Кроме того, он продолжает выполнять следующие строки.
  • Который оказался return true, если ни один из двух прыжков не был сделан.
  • И тогда у нас есть строки, помеченные как L1, которые являются return false.

Однако метод AndNonSC (&) генерирует условные прыжки три!

  • Он загружает value и x в стек и переходит к L1, если value ниже. Поскольку теперь ему нужно сохранить результат, чтобы сравнить его с другой частью И, поэтому он должен выполнить либо "сохранить true", либо "сохранить false", он не может выполнять оба действия с одной и той же инструкцией.
  • Он загружает value и y в стек и переходит к L1, если value больше. Еще раз ему нужно сохранить true или false и что две разные строки зависят от результата сравнения.
  • Теперь, когда оба сравнения выполнены, код фактически выполняет операцию И - и если оба они истинны, он перескакивает (в третий раз), чтобы возвращать true; или же он продолжает выполнение на следующей строке, чтобы вернуть false.

(предварительный) Заключение

Хотя я не так сильно разбираюсь в байт-коде Java, и я, возможно, что-то упустил из виду, мне кажется, что & будет выполнять хуже, чем && в каждом случае: он генерирует больше инструкций для выполнения, включая более условные скачки для прогнозирования и, возможно, сбоя.

Переписывание кода для замены сравнений с арифметическими операциями, как и другие, предлагаемые, может быть способом сделать & лучшим вариантом, но ценой сделать код намного менее ясным.
ИМХО, это не стоит хлопот для 99% сценариев (это может быть очень полезно для 1% циклов, которые должны быть чрезвычайно оптимизированы, хотя).

EDIT: сборка AMD64

Как отмечалось в комментариях, один и тот же байт-код Java может привести к разному машинным кодам в разных системах, поэтому, хотя байт-код Java может дать нам подсказку о том, какая версия И работает лучше, получение фактической ASM, сгенерированной компилятором, единственный способ действительно узнать.
Я напечатал инструкции AMD64 ASM для обоих методов; ниже приведены соответствующие строки (удаленные точки входа и т.д.).

ПРИМЕЧАНИЕ: все методы, скомпилированные с помощью java 1.8.0_91, если не указано иное.

Метод AndSC с параметрами по умолчанию

  # {method} {0x0000000016da0810} 'AndSC' '(III)Z' in 'AndTest'
  ...
  0x0000000002923e3e: cmp    %r8d,%r9d
  0x0000000002923e41: movabs $0x16da0a08,%rax   ;   {metadata(method data for {method} {0x0000000016da0810} 'AndSC' '(III)Z' in 'AndTest')}
  0x0000000002923e4b: movabs $0x108,%rsi
  0x0000000002923e55: jl     0x0000000002923e65
  0x0000000002923e5b: movabs $0x118,%rsi
  0x0000000002923e65: mov    (%rax,%rsi,1),%rbx
  0x0000000002923e69: lea    0x1(%rbx),%rbx
  0x0000000002923e6d: mov    %rbx,(%rax,%rsi,1)
  0x0000000002923e71: jl     0x0000000002923eb0  ;*if_icmplt
                                                ; - AndTest::[email protected] (line 22)

  0x0000000002923e77: cmp    %edi,%r9d
  0x0000000002923e7a: movabs $0x16da0a08,%rax   ;   {metadata(method data for {method} {0x0000000016da0810} 'AndSC' '(III)Z' in 'AndTest')}
  0x0000000002923e84: movabs $0x128,%rsi
  0x0000000002923e8e: jg     0x0000000002923e9e
  0x0000000002923e94: movabs $0x138,%rsi
  0x0000000002923e9e: mov    (%rax,%rsi,1),%rdi
  0x0000000002923ea2: lea    0x1(%rdi),%rdi
  0x0000000002923ea6: mov    %rdi,(%rax,%rsi,1)
  0x0000000002923eaa: jle    0x0000000002923ec1  ;*if_icmpgt
                                                ; - AndTest::[email protected] (line 22)

  0x0000000002923eb0: mov    $0x0,%eax
  0x0000000002923eb5: add    $0x30,%rsp
  0x0000000002923eb9: pop    %rbp
  0x0000000002923eba: test   %eax,-0x1c73dc0(%rip)        # 0x0000000000cb0100
                                                ;   {poll_return}
  0x0000000002923ec0: retq                      ;*ireturn
                                                ; - AndTest::[email protected] (line 25)

  0x0000000002923ec1: mov    $0x1,%eax
  0x0000000002923ec6: add    $0x30,%rsp
  0x0000000002923eca: pop    %rbp
  0x0000000002923ecb: test   %eax,-0x1c73dd1(%rip)        # 0x0000000000cb0100
                                                ;   {poll_return}
  0x0000000002923ed1: retq   

Метод AndSC с опцией -XX:PrintAssemblyOptions=intel

  # {method} {0x00000000170a0810} 'AndSC' '(III)Z' in 'AndTest'
  ...
  0x0000000002c26e2c: cmp    r9d,r8d
  0x0000000002c26e2f: jl     0x0000000002c26e36  ;*if_icmplt
  0x0000000002c26e31: cmp    r9d,edi
  0x0000000002c26e34: jle    0x0000000002c26e44  ;*iconst_0
  0x0000000002c26e36: xor    eax,eax            ;*synchronization entry
  0x0000000002c26e38: add    rsp,0x10
  0x0000000002c26e3c: pop    rbp
  0x0000000002c26e3d: test   DWORD PTR [rip+0xffffffffffce91bd],eax        # 0x0000000002910000
  0x0000000002c26e43: ret    
  0x0000000002c26e44: mov    eax,0x1
  0x0000000002c26e49: jmp    0x0000000002c26e38

Метод AndNonSC с параметрами по умолчанию

  # {method} {0x0000000016da0908} 'AndNonSC' '(III)Z' in 'AndTest'
  ...
  0x0000000002923a78: cmp    %r8d,%r9d
  0x0000000002923a7b: mov    $0x0,%eax
  0x0000000002923a80: jl     0x0000000002923a8b
  0x0000000002923a86: mov    $0x1,%eax
  0x0000000002923a8b: cmp    %edi,%r9d
  0x0000000002923a8e: mov    $0x0,%esi
  0x0000000002923a93: jg     0x0000000002923a9e
  0x0000000002923a99: mov    $0x1,%esi
  0x0000000002923a9e: and    %rsi,%rax
  0x0000000002923aa1: cmp    $0x0,%eax
  0x0000000002923aa4: je     0x0000000002923abb  ;*ifeq
                                                ; - AndTest::[email protected] (line 29)

  0x0000000002923aaa: mov    $0x1,%eax
  0x0000000002923aaf: add    $0x30,%rsp
  0x0000000002923ab3: pop    %rbp
  0x0000000002923ab4: test   %eax,-0x1c739ba(%rip)        # 0x0000000000cb0100
                                                ;   {poll_return}
  0x0000000002923aba: retq                      ;*ireturn
                                                ; - AndTest::[email protected] (line 30)

  0x0000000002923abb: mov    $0x0,%eax
  0x0000000002923ac0: add    $0x30,%rsp
  0x0000000002923ac4: pop    %rbp
  0x0000000002923ac5: test   %eax,-0x1c739cb(%rip)        # 0x0000000000cb0100
                                                ;   {poll_return}
  0x0000000002923acb: retq   

Метод AndNonSC с опцией -XX:PrintAssemblyOptions=intel

  # {method} {0x00000000170a0908} 'AndNonSC' '(III)Z' in 'AndTest'
  ...
  0x0000000002c270b5: cmp    r9d,r8d
  0x0000000002c270b8: jl     0x0000000002c270df  ;*if_icmplt
  0x0000000002c270ba: mov    r8d,0x1            ;*iload_2
  0x0000000002c270c0: cmp    r9d,edi
  0x0000000002c270c3: cmovg  r11d,r10d
  0x0000000002c270c7: and    r8d,r11d
  0x0000000002c270ca: test   r8d,r8d
  0x0000000002c270cd: setne  al
  0x0000000002c270d0: movzx  eax,al
  0x0000000002c270d3: add    rsp,0x10
  0x0000000002c270d7: pop    rbp
  0x0000000002c270d8: test   DWORD PTR [rip+0xffffffffffce8f22],eax        # 0x0000000002910000
  0x0000000002c270de: ret    
  0x0000000002c270df: xor    r8d,r8d
  0x0000000002c270e2: jmp    0x0000000002c270c0
  • Прежде всего, сгенерированный код ASM отличается в зависимости от того, выбираем ли мы синтаксис AT & T по умолчанию или синтаксис Intel.
  • С синтаксисом AT & T:
    • Код ASM на самом деле длиннее для метода AndSC, при этом каждый байт-код IF_ICMP* переводится в две инструкции перехода на сборку, в общей сложности 4 условных перехода.
    • Между тем, для метода AndNonSC компилятор генерирует более прямой код, где каждый байт-код IF_ICMP* преобразуется только в одну команду перехода в сборку, сохраняя исходное количество 3 условных переходов.
  • С синтаксисом Intel:
    • Код ASM для AndSC короче, всего 2 условных перехода (не считая в конце условного jmp). На самом деле это всего две CMP, два JL/E и XOR/MOV в зависимости от результата.
    • Код ASM для AndNonSC теперь длиннее, чем AndSC! Однако он имеет только 1 условный переход (для первого сравнения), используя регистры для прямого сравнения первого результата со вторым, без каких-либо переходов.

Заключение после анализа кода ASM

  • На уровне машинного языка AMD64 оператор &, по-видимому, генерирует код ASM с меньшим количеством условных переходов, что может быть лучше при высоких частотах ошибок прогнозирования (например, случайный value).
  • С другой стороны, оператор &&, по-видимому, генерирует код ASM с меньшим количеством инструкций (с параметром -XX:PrintAssemblyOptions=intel в любом случае), что может быть лучше для очень длинных циклов с удобными для прогнозирования вводами, где меньшее количество Циклы CPU для каждого сравнения могут иметь значение в долгосрочной перспективе.

Как я уже сказал в некоторых комментариях, это будет сильно различаться между системами, поэтому, если мы говорим об оптимизации прогнозирования ветвей, единственным реальным ответом будет: это зависит от вашей реализации JVM, ваш компилятор, ваш процессор и ваши входные данные.


Приложение: метод Guava isPowerOfTwo

Здесь разработчики Guava придумали опрятный способ вычисления, если заданное число имеет мощность 2:

public static boolean isPowerOfTwo(long x) {
    return x > 0 & (x & (x - 1)) == 0;
}

Цитирование OP:

это использование & (где && было бы более нормальным) реальная оптимизация?

Чтобы узнать, есть ли это, я добавил два похожих метода в свой тестовый класс:

public boolean isPowerOfTwoAND(long x) {
    return x > 0 & (x & (x - 1)) == 0;
}

public boolean isPowerOfTwoANDAND(long x) {
    return x > 0 && (x & (x - 1)) == 0;
}

Код ASM для версии Guava

  # {method} {0x0000000017580af0} 'isPowerOfTwoAND' '(J)Z' in 'AndTest'
  # this:     rdx:rdx   = 'AndTest'
  # parm0:    r8:r8     = long
  ...
  0x0000000003103bbe: movabs rax,0x0
  0x0000000003103bc8: cmp    rax,r8
  0x0000000003103bcb: movabs rax,0x175811f0     ;   {metadata(method data for {method} {0x0000000017580af0} 'isPowerOfTwoAND' '(J)Z' in 'AndTest')}
  0x0000000003103bd5: movabs rsi,0x108
  0x0000000003103bdf: jge    0x0000000003103bef
  0x0000000003103be5: movabs rsi,0x118
  0x0000000003103bef: mov    rdi,QWORD PTR [rax+rsi*1]
  0x0000000003103bf3: lea    rdi,[rdi+0x1]
  0x0000000003103bf7: mov    QWORD PTR [rax+rsi*1],rdi
  0x0000000003103bfb: jge    0x0000000003103c1b  ;*lcmp
  0x0000000003103c01: movabs rax,0x175811f0     ;   {metadata(method data for {method} {0x0000000017580af0} 'isPowerOfTwoAND' '(J)Z' in 'AndTest')}
  0x0000000003103c0b: inc    DWORD PTR [rax+0x128]
  0x0000000003103c11: mov    eax,0x1
  0x0000000003103c16: jmp    0x0000000003103c20  ;*goto
  0x0000000003103c1b: mov    eax,0x0            ;*lload_1
  0x0000000003103c20: mov    rsi,r8
  0x0000000003103c23: movabs r10,0x1
  0x0000000003103c2d: sub    rsi,r10
  0x0000000003103c30: and    rsi,r8
  0x0000000003103c33: movabs rdi,0x0
  0x0000000003103c3d: cmp    rsi,rdi
  0x0000000003103c40: movabs rsi,0x175811f0     ;   {metadata(method data for {method} {0x0000000017580af0} 'isPowerOfTwoAND' '(J)Z' in 'AndTest')}
  0x0000000003103c4a: movabs rdi,0x140
  0x0000000003103c54: jne    0x0000000003103c64
  0x0000000003103c5a: movabs rdi,0x150
  0x0000000003103c64: mov    rbx,QWORD PTR [rsi+rdi*1]
  0x0000000003103c68: lea    rbx,[rbx+0x1]
  0x0000000003103c6c: mov    QWORD PTR [rsi+rdi*1],rbx
  0x0000000003103c70: jne    0x0000000003103c90  ;*lcmp
  0x0000000003103c76: movabs rsi,0x175811f0     ;   {metadata(method data for {method} {0x0000000017580af0} 'isPowerOfTwoAND' '(J)Z' in 'AndTest')}
  0x0000000003103c80: inc    DWORD PTR [rsi+0x160]
  0x0000000003103c86: mov    esi,0x1
  0x0000000003103c8b: jmp    0x0000000003103c95  ;*goto
  0x0000000003103c90: mov    esi,0x0            ;*iand
  0x0000000003103c95: and    rsi,rax
  0x0000000003103c98: and    esi,0x1
  0x0000000003103c9b: mov    rax,rsi
  0x0000000003103c9e: add    rsp,0x50
  0x0000000003103ca2: pop    rbp
  0x0000000003103ca3: test   DWORD PTR [rip+0xfffffffffe44c457],eax        # 0x0000000001550100
  0x0000000003103ca9: ret    

Код Intel asm для версии &&

  # {method} {0x0000000017580bd0} 'isPowerOfTwoANDAND' '(J)Z' in 'AndTest'
  # this:     rdx:rdx   = 'AndTest'
  # parm0:    r8:r8     = long
  ...
  0x0000000003103438: movabs rax,0x0
  0x0000000003103442: cmp    rax,r8
  0x0000000003103445: jge    0x0000000003103471  ;*lcmp
  0x000000000310344b: mov    rax,r8
  0x000000000310344e: movabs r10,0x1
  0x0000000003103458: sub    rax,r10
  0x000000000310345b: and    rax,r8
  0x000000000310345e: movabs rsi,0x0
  0x0000000003103468: cmp    rax,rsi
  0x000000000310346b: je     0x000000000310347b  ;*lcmp
  0x0000000003103471: mov    eax,0x0
  0x0000000003103476: jmp    0x0000000003103480  ;*ireturn
  0x000000000310347b: mov    eax,0x1            ;*goto
  0x0000000003103480: and    eax,0x1
  0x0000000003103483: add    rsp,0x40
  0x0000000003103487: pop    rbp
  0x0000000003103488: test   DWORD PTR [rip+0xfffffffffe44cc72],eax        # 0x0000000001550100
  0x000000000310348e: ret    

В этом конкретном примере компилятор JIT генерирует гораздо меньше кода сборки для версии &&, чем для версии Guava & (и, после вчерашних результатов, я был искренне удивлен этим). По сравнению с версией Guava версия && переводит на 25% меньше байт-кода для компиляции JIT, на 50% меньше инструкций по сборке и только два условных перехода (версия & имеет четыре из них).

Итак, все указывает на то, что метод Guava & менее эффективен, чем более "естественная" версия &&.

... Или это?

Как отмечалось ранее, я запускаю приведенные выше примеры с Java 8:

C:\....>java -version
java version "1.8.0_91"
Java(TM) SE Runtime Environment (build 1.8.0_91-b14)
Java HotSpot(TM) 64-Bit Server VM (build 25.91-b14, mixed mode)

Но что делать, если я переключусь на Java 7?

C:\....>c:\jdk1.7.0_79\bin\java -version
java version "1.7.0_79"
Java(TM) SE Runtime Environment (build 1.7.0_79-b15)
Java HotSpot(TM) 64-Bit Server VM (build 24.79-b02, mixed mode)
C:\....>c:\jdk1.7.0_79\bin\java -XX:+UnlockDiagnosticVMOptions -XX:CompileCommand=print,*AndTest.isPowerOfTwoAND -XX:PrintAssemblyOptions=intel AndTestMain
  .....
  0x0000000002512bac: xor    r10d,r10d
  0x0000000002512baf: mov    r11d,0x1
  0x0000000002512bb5: test   r8,r8
  0x0000000002512bb8: jle    0x0000000002512bde  ;*ifle
  0x0000000002512bba: mov    eax,0x1            ;*lload_1
  0x0000000002512bbf: mov    r9,r8
  0x0000000002512bc2: dec    r9
  0x0000000002512bc5: and    r9,r8
  0x0000000002512bc8: test   r9,r9
  0x0000000002512bcb: cmovne r11d,r10d
  0x0000000002512bcf: and    eax,r11d           ;*iand
  0x0000000002512bd2: add    rsp,0x10
  0x0000000002512bd6: pop    rbp
  0x0000000002512bd7: test   DWORD PTR [rip+0xffffffffffc0d423],eax        # 0x0000000002120000
  0x0000000002512bdd: ret    
  0x0000000002512bde: xor    eax,eax
  0x0000000002512be0: jmp    0x0000000002512bbf
  .....

Сюрприз! Сводный код, сгенерированный для метода & компилятором JIT в Java 7, теперь имеет только один условный переход и является короче! В то время как метод && (вам придется доверять мне на этом, я не хочу загромождать окончание!) Остается примерно таким же, с его двумя условными переходами и еще несколькими инструкциями, вершинами.
Похоже, инженеры Гува знали, что они делают, в конце концов! (если они пытались оптимизировать время выполнения Java 7, то есть: -)

Итак, вернемся к последнему вопросу OP:

это использование & (где && было бы более нормальным) реальная оптимизация?

И IMHO ответ тот же, даже для этого (очень!) конкретного сценария: зависит от вашей реализации JVM, вашего компилятора, вашего процессора и ваших входных данных.

Ответ 2

По этим вопросам вы должны запустить микрозапуск. Я использовал JMH для этого теста.

Тесты выполняются как

// boolean logical AND
bh.consume(value >= x & y <= value);

и

// conditional AND
bh.consume(value >= x && y <= value);

и

// bitwise OR, as suggested by Joop Eggen
bh.consume(((value - x) | (y - value)) >= 0)

Со значениями value, x and y в соответствии с именем теста.

Результат (пять разминок и десять итераций измерения) для сравнения производительности:

Benchmark                                 Mode  Cnt    Score    Error   Units
Benchmark.isBooleanANDBelowRange          thrpt   10  386.086 ▒ 17.383  ops/us
Benchmark.isBooleanANDInRange             thrpt   10  387.240 ▒  7.657  ops/us
Benchmark.isBooleanANDOverRange           thrpt   10  381.847 ▒ 15.295  ops/us
Benchmark.isBitwiseORBelowRange           thrpt   10  384.877 ▒ 11.766  ops/us
Benchmark.isBitwiseORInRange              thrpt   10  380.743 ▒ 15.042  ops/us
Benchmark.isBitwiseOROverRange            thrpt   10  383.524 ▒ 16.911  ops/us
Benchmark.isConditionalANDBelowRange      thrpt   10  385.190 ▒ 19.600  ops/us
Benchmark.isConditionalANDInRange         thrpt   10  384.094 ▒ 15.417  ops/us
Benchmark.isConditionalANDOverRange       thrpt   10  380.913 ▒  5.537  ops/us

Результат не так уж и отличается для самой оценки. До тех пор, пока на этом фрагменте кода не обнаружено никакого эффекта, я бы не попытался его оптимизировать. В зависимости от места в коде компилятор hotspot может решить сделать некоторую оптимизацию. Который, вероятно, не охвачен вышеуказанными критериями.

несколько ссылок:

логический логический И - значение результата true, если оба значения операнда true; в противном случае результат false
условный AND - как &, но оценивает его правый операнд, только если значение его левого операнда true
побитовое ИЛИ - значение результата является побитовым включением ИЛИ значений операнда

Ответ 3

Я собираюсь прийти к этому под другим углом.

Рассмотрим эти два фрагмента кода,

  if (value >= x && value <= y) {

и

  if (value >= x & value <= y) {

Если мы предположим, что value, x, y имеют примитивный тип, то эти два (частичных) оператора будут давать одинаковый результат для всех возможных входных значений. (Если задействованы типы-оболочки, то они не совсем эквивалентны из-за неявного теста null для y, который может выйти из строя в версии &, а не версии &&.)

Если компилятор JIT делает хорошую работу, его оптимизатор сможет вывести, что эти два оператора делают одно и то же:

  • Если кто-то предсказуем быстрее, чем другой, то он должен иметь возможность использовать более быструю версию... в компилированном коде JIT.

  • Если нет, то не имеет значения, какая версия используется на уровне исходного кода.

  • Поскольку компилятор JIT собирает статистику пути перед компиляцией, он потенциально может иметь больше информации о характеристиках выполнения, которые программист (!).

  • Если компилятор JIT текущего поколения (на любой данной платформе) недостаточно оптимизирован, чтобы справиться с этим, следующее поколение могло бы хорошо... в зависимости от того, указывает ли эмпирические данные на то, является ли это стоящим шаблон для оптимизации.

  • В самом деле, если вы пишете Java-код таким образом, который оптимизирует для этого, есть вероятность, что, выбрав более "непонятную" версию кода, вы можете заблокировать текущую или будущую способность компилятора JIT к оптимизировать.

Короче говоря, я не думаю, что вы должны сделать такую ​​микро-оптимизацию на уровне исходного кода. И если вы принимаете этот аргумент 1 и следуете его логическому завершению, вопрос о том, какая версия быстрее,... moot 2.

1 - Я не утверждаю, что это нигде не является доказательством.

2 - Если вы не являетесь одним из крошечных сообществ людей, которые на самом деле пишут компиляторы Java JIT...


"Очень известный вопрос" интересен в двух отношениях:

  • С одной стороны, это пример, в котором вид оптимизации, требуемый для изменения, намного превосходит возможности компилятора JIT.

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

Ответ 4

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

Используя & over &&, чтобы сохранить наносекунду, если в некоторых очень редких ситуациях бессмысленно, вы уже потратили больше времени на размышления о различии, чем вы бы сохранили, используя & over &&.

Edit

Мне стало любопытно, и я решил запустить некоторые следы.

Я сделал этот класс:

public class Main {

    static int x = 22, y = 48;

    public static void main(String[] args) {
        runWithOneAnd(30);
        runWithTwoAnds(30);
    }

    static void runWithOneAnd(int value){
        if(value >= x & value <= y){

        }
    }

    static void runWithTwoAnds(int value){
        if(value >= x && value <= y){

        }
    }
}

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

Первый тест:

Первый тест профилирования

Второй тест:

Второй тест профилирования

Третий тест:

Третий тест профилирования

Как вы можете видеть в тестах профилирования, использование только одного & выполняется в 2-3 раза дольше, чем при использовании двух &&. Это действительно как-то странно, как я ожидал лучшей производительности только от одного &.

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

Мораль истории: соглашение хорошее, а преждевременная оптимизация - плохая.


Изменить 2

Я удалил контрольный код с комментариями @SvetlinZarev и несколькими другими улучшениями. Вот модифицированный эталонный код:

public class Main {

    static int x = 22, y = 48;

    public static void main(String[] args) {
        oneAndBothTrue();
        oneAndOneTrue();
        oneAndBothFalse();
        twoAndsBothTrue();
        twoAndsOneTrue();
        twoAndsBothFalse();
        System.out.println(b);
    }

    static void oneAndBothTrue() {
        int value = 30;
        for (int i = 0; i < 2000; i++) {
            if (value >= x & value <= y) {
                doSomething();
            }
        }
    }

    static void oneAndOneTrue() {
        int value = 60;
        for (int i = 0; i < 4000; i++) {
            if (value >= x & value <= y) {
                doSomething();
            }
        }
    }

    static void oneAndBothFalse() {
        int value = 100;
        for (int i = 0; i < 4000; i++) {
            if (value >= x & value <= y) {
                doSomething();
            }
        }
    }

    static void twoAndsBothTrue() {
        int value = 30;
        for (int i = 0; i < 4000; i++) {
            if (value >= x & value <= y) {
                doSomething();
            }
        }
    }

    static void twoAndsOneTrue() {
        int value = 60;
        for (int i = 0; i < 4000; i++) {
            if (value >= x & value <= y) {
                doSomething();
            }
        }
    }

    static void twoAndsBothFalse() {
        int value = 100;
        for (int i = 0; i < 4000; i++) {
            if (value >= x & value <= y) {
                doSomething();
            }
        }
    }

    //I wanted to avoid print statements here as they can
    //affect the benchmark results. 
    static StringBuilder b = new StringBuilder();
    static int times = 0;

    static void doSomething(){
        times++;
        b.append("I have run ").append(times).append(" times \n");
    }
}

И вот тесты производительности:

Тест 1:

введите описание изображения здесь

Тест 2:

введите описание изображения здесь

Тест 3:

введите описание изображения здесь

Это учитывает различные значения и различные условия.

При использовании одного & требуется больше времени для запуска, когда оба условия истинны, примерно на 60% или на 2 миллисекунды больше времени. Если одно или оба условия ложны, один & работает быстрее, но он работает только на 0,30-0,50 миллисекунды быстрее. Таким образом, & будет работать быстрее, чем && в большинстве случаев, но разница в производительности все равно незначительна.

Ответ 5

То, что вам нужно, это примерно так:

x <= value & value <= y
value - x >= 0 & y - value >= 0
((value - x) | (y - value)) >= 0  // integer bit-or

Интересно, хотелось бы посмотреть на байтовый код. Но трудно сказать. Я бы хотел, чтобы это было вопросом C.

Ответ 6

Мне тоже было интересно ответить, поэтому я написал следующий (простой) тест для этого:

private static final int max = 80000;
private static final int size = 100000;
private static final int x = 1500;
private static final int y = 15000;
private Random random;

@Before
public void setUp() {
    this.random = new Random();
}

@After
public void tearDown() {
    random = null;
}

@Test
public void testSingleOperand() {
    int counter = 0;
    int[] numbers = new int[size];
    for (int j = 0; j < size; j++) {
        numbers[j] = random.nextInt(max);
    }

    long start = System.nanoTime(); //start measuring after an array has been filled
    for (int i = 0; i < numbers.length; i++) {
        if (numbers[i] >= x & numbers[i] <= y) {
            counter++;
        }
    }
    long end = System.nanoTime();
    System.out.println("Duration of single operand: " + (end - start));
}

@Test
public void testDoubleOperand() {
    int counter = 0;
    int[] numbers = new int[size];
    for (int j = 0; j < size; j++) {
        numbers[j] = random.nextInt(max);
    }

    long start = System.nanoTime(); //start measuring after an array has been filled
    for (int i = 0; i < numbers.length; i++) {
        if (numbers[i] >= x & numbers[i] <= y) {
            counter++;
        }
    }
    long end = System.nanoTime();
    System.out.println("Duration of double operand: " + (end - start));
}

С конечным результатом является то, что сравнение с && всегда выигрывает с точки зрения скорости, примерно на 1,5/2 миллисекунды быстрее, чем &.

EDIT: Как отметил @SvetlinZarev, я также измерял время, которое потребовалось Random, чтобы получить целое число. Изменил его для использования предварительно заполненного массива случайных чисел, что вызвало сильное колебание продолжительности одного теста операнда; различия между несколькими прогонами были до 6-7 мс.

Ответ 7

Как мне объяснили, это то, что && вернет false, если первая проверка в серии ложна, а также проверяет все элементы в серии, независимо от того, сколько из них ложно. И.Е.

если (x > 0 & x <= 10 && x

Будет работать быстрее, чем

если (x > 0 и x <= 10 и x

Если x больше 10, поскольку одиночные амперсанды будут продолжать проверять остальные условия, тогда как двойные амперсанды будут ломаться после первого неверного условия.