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

в этом коде:

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

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

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

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

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

Примечания:

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

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

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

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

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

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

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

7 ответов


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

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

источник 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;
}

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

байт-код 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 (&&) метод генерирует два условные прыжки, как и ожидалось:

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

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

  1. это нагрузки value и x на стек и переходит к L1, Если value ниже. Потому что теперь ему нужно сохранить результат, чтобы сравнить его с другой частью AND, поэтому он должен выполнить либо " save true" или "сохранить false", он не может делать оба с одной и той же инструкцией.
  2. он загружает value и y на стек и переходит к L1, Если value больше. Еще раз его нужно спасти true или false и это две разные линии в зависимости от сравнения результат.
  3. теперь и сравнения сделаны, код фактически выполняет операцию и-и если оба истинны, он прыгает (в третий раз), чтобы вернуть 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 x16da0a08,%rax   ;   {metadata(method data for {method} {0x0000000016da0810} 'AndSC' '(III)Z' in 'AndTest')}
  0x0000000002923e4b: movabs x108,%rsi
  0x0000000002923e55: jl     0x0000000002923e65
  0x0000000002923e5b: movabs x118,%rsi
  0x0000000002923e65: mov    (%rax,%rsi,1),%rbx
  0x0000000002923e69: lea    0x1(%rbx),%rbx
  0x0000000002923e6d: mov    %rbx,(%rax,%rsi,1)
  0x0000000002923e71: jl     0x0000000002923eb0  ;*if_icmplt
                                                ; - AndTest::AndSC@2 (line 22)

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

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

  0x0000000002923ec1: mov    x1,%eax
  0x0000000002923ec6: add    x30,%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    x0,%eax
  0x0000000002923a80: jl     0x0000000002923a8b
  0x0000000002923a86: mov    x1,%eax
  0x0000000002923a8b: cmp    %edi,%r9d
  0x0000000002923a8e: mov    x0,%esi
  0x0000000002923a93: jg     0x0000000002923a9e
  0x0000000002923a99: mov    x1,%esi
  0x0000000002923a9e: and    %rsi,%rax
  0x0000000002923aa1: cmp    x0,%eax
  0x0000000002923aa4: je     0x0000000002923abb  ;*ifeq
                                                ; - AndTest::AndNonSC@21 (line 29)

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

  0x0000000002923abb: mov    x0,%eax
  0x0000000002923ac0: add    x30,%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 с меньшим количеством условных скачков, что может быть лучше для high прогнозирование-частота отказов (random values, например).
  • С другой стороны,&& оператор, похоже, генерирует код ASM с меньшим количеством инструкций (с -XX:PrintAssemblyOptions=intel вариант в любом случае), что может быть лучше для очень долго петли с удобными для прогнозирования входами, где меньшее количество циклов процессора для каждого сравнения может иметь значение в долгосрочной перспективе.

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


добавление: гуава это 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 Intel для версии 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    

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

  # {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 генерирует далеко меньше кода сборки для && версия чем для гуавы & версия (и, после вчерашних результатов, я был искренне удивлен этим).
По сравнению с гуавой,&& версия переводится на 25% меньше байт-кода для JIT для компиляции, на 50% меньше инструкций по сборке и только два условных перехода (& версия есть четыре из них).

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

... Или нет?

As как отмечалось ранее, я запускаю приведенные выше примеры с 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:

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

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


для таких вопросов вы должны запустить microbenchmark. Я использовал 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
условное И - это...&, но оценивает его правый операнд, только если значение его левого операнда true
побитовое или - значение результата является побитовым включительным или значений операнда


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

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

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

и

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

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


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

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

редактировать

мне стало любопытно,и я решил пробежать несколько скамеек.

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

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.

первый тест:

The first profiling test

второй тест:

The second profiling test

третий тест:

The third profiling test

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

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

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


Edit 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");
    }
}

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


то, что вам нужно, - это что-то вроде этого:

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

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


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

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-7ms.


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

if (x>0 & & x

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

if (x>0 & x

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