Ok, si vous voulez savoir comment il se comporte au niveau le plus bas... nous allons avoir un regard sur le bytecode puis!
EDIT : ajouté le code assembleur généré pour AMD64, à la fin. Regardez les quelques notes intéressantes.
EDIT 2 (re: OP de mise à Jour "2"): ajout de l'asm code de la Goyave de l' isPowerOfTwo
méthode .
Source Java
J'ai écrit ces deux méthodes rapides:
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;
}
Comme vous pouvez le voir, ils sont exactement les mêmes, sauf pour le type d'opérateur ET.
Le bytecode Java
Et c'est le bytecode généré:
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
L' AndSC
(&&
) méthode génère deux sauts conditionnels, comme prévu:
- Il charge
value
et x
sur la pile, et les sauts de L1 si value
est plus faible. Sinon, il continue de courir les prochaines lignes.
- Il charge
value
et y
sur la pile, et les sauts de L1 aussi, si value
est plus grand. Sinon, il continue de courir les prochaines lignes.
- Ce qui arrive,
return true
dans le cas où aucun des deux sauts ont été effectués.
- Et puis nous avons les lignes marquées en tant que L1 qui sont un
return false
.
L' AndNonSC
(&
) méthode, cependant, génère trois conditionnelle sauts!
- Il charge
value
et x
sur la pile et les sauts de L1 si value
est plus faible. Parce que maintenant il a besoin pour enregistrer le résultat de la comparer avec l'autre partie de la ET de, de sorte qu'il a à exécuter, soit de "sauver true
" ou "enregistrer l' false
", il ne peut pas faire les deux avec la même instruction.
- Il charge
value
et y
sur la pile et les sauts de L1 si value
est plus grand. Une fois encore, il a besoin pour sauver true
ou false
et que les deux lignes différentes en fonction du résultat de la comparaison.
- Maintenant que les deux comparaisons sont faites, le code s'exécute en fait l'opération -- et si les deux sont vrais, il saute (une troisième fois), renvoie vrai; sinon, il continue l'exécution à la ligne suivante pour renvoyer false.
Conclusion (Préliminaire)
Si je ne suis pas très expérimenté avec le bytecode Java et j'ai peut-être oublié quelque chose, il me semble qu' &
pour exécuter pire qu' &&
dans tous les cas: il génère plus d'instructions à exécuter, y compris la conditionnalité des sauts à prévoir et éventuellement échouer.
Une réécriture du code pour remplacer les comparaisons avec des opérations arithmétiques, comme quelqu'un d'autre a proposé, pourrait être un moyen de rendre l' &
une meilleure option, mais au prix de rendre le code moins clair.
À mon humble avis il n'est pas en valeur la dispute pour 99% des scénarios (c'est peut-être très bien la peine pour les 1% les boucles qui doivent être très optimisé, tout de même).
EDIT: AMD64 assemblée
Comme indiqué dans les commentaires, le même bytecode Java peut entraîner des différences dans le code machine dans les différents systèmes, ainsi, alors que le bytecode Java pourrait nous donner un indice sur qui ET version plus performante, l'obtention de l'effectif de l'ASM généré par le compilateur est la seule façon de vraiment savoir.
J'ai imprimé la AMD64 ASM instructions pour les deux méthodes ci-dessous sont les lignes (dépouillé de points d'entrée, etc.).
REMARQUE: toutes les méthodes compilé avec java 1.8.0_91, sauf indication contraire.
Méthode AndSC
avec les options par défaut
# {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::AndSC@2 (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::AndSC@7 (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::AndSC@13 (line 25)
0x0000000002923ec1: mov $0x1,%eax
0x0000000002923ec6: add $0x30,%rsp
0x0000000002923eca: pop %rbp
0x0000000002923ecb: test %eax,-0x1c73dd1(%rip) # 0x0000000000cb0100
; {poll_return}
0x0000000002923ed1: retq
Méthode AndSC
avec -XX:PrintAssemblyOptions=intel
option
# {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
Méthode AndNonSC
avec les options par défaut
# {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::AndNonSC@21 (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::AndNonSC@25 (line 30)
0x0000000002923abb: mov $0x0,%eax
0x0000000002923ac0: add $0x30,%rsp
0x0000000002923ac4: pop %rbp
0x0000000002923ac5: test %eax,-0x1c739cb(%rip) # 0x0000000000cb0100
; {poll_return}
0x0000000002923acb: retq
Méthode AndNonSC
avec -XX:PrintAssemblyOptions=intel
option
# {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
- Tout d'abord, à la génération de code ASM diffère selon que l'on choisisse le défaut d'AT&T de la syntaxe ou de la syntaxe Intel.
- Avec AT&T syntaxe:
- L'ASM code est en fait plus pour l'
AndSC
méthode, avec tous les bytecode IF_ICMP*
convertis en deux de l'assemblée des instructions de saut, pour un total de 4 sauts conditionnels.
- En attendant, pour l'
AndNonSC
méthode, le compilateur génère une plus straight-forward code, où chaque bytecode IF_ICMP*
est traduite en une seule assemblée instruction de saut, en gardant l'original du décompte de 3 sauts conditionnels.
- Avec Intel syntaxe:
- L'ASM code pour
AndSC
est plus courte, avec seulement 2 sauts conditionnels (sans compter les non-conditionnel jmp
à la fin). En fait, c'est juste deux CMP, deux JL/E et un XOR/MOV en fonction du résultat.
- L'ASM code pour
AndNonSC
est maintenant plus que l' AndSC
! Cependant, il a juste 1 saut conditionnel (pour la comparaison), en utilisant les registres de comparer directement le premier résultat avec la seconde, sans plus de sauts.
Après la Conclusion de l'ASM d'analyse de code
- Au AMD64 machine-niveau de langue, l'
&
opérateur semble générer ASM code avec moins de sauts conditionnels, ce qui pourrait être mieux pour haute prédiction du taux d'échec (random value
s par exemple).
- D'autre part, l'
&&
opérateur semble générer ASM code avec moins d'instructions (avec l' -XX:PrintAssemblyOptions=intel
option de toute façon), ce qui pourrait être mieux pour vraiment long d'une boucle de prédiction-friendly entrées, où la diminution du nombre de cycles CPU pour chaque comparaison peut faire une différence dans le long terme.
Comme je l'ai dit dans certains commentaires, cela va varier considérablement entre les systèmes, de sorte que si nous parlons de la direction de la prévision d'optimisation, la seule vraie réponse est: cela dépend de votre JVM, votre compilateur, votre PROCESSEUR et de votre entrée de données.
Addendum: Goyave l' isPowerOfTwo
méthode
Ici, la Goyave, les développeurs ont trouvé une façon ingénieuse de calcul si un nombre est une puissance de 2:
public static boolean isPowerOfTwo(long x) {
return x > 0 & (x & (x - 1)) == 0;
}
Citant l'OP:
est-ce l'utilisation de &
(où &&
serait plus normal) une réelle optimisation?
Pour savoir si elle l'est, j'ai ajouté deux des méthodes similaires pour ma classe de test:
public boolean isPowerOfTwoAND(long x) {
return x > 0 & (x & (x - 1)) == 0;
}
public boolean isPowerOfTwoANDAND(long x) {
return x > 0 && (x & (x - 1)) == 0;
}
Intel ASM code pour la version de Goyave
# {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 code pour &&
version
# {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
Dans cet exemple précis, le compilateur JIT génère beaucoup moins de code assembleur pour l' &&
version que pour la Goyave de l' &
version (et, d'après les résultats d'hier, j'ai été franchement surpris par cette).
Par rapport à la Goyave, l' &&
version se traduit par 25% de moins de bytecode pour JIT compiler, 50% de moins que les instructions de montage, et seulement deux sauts conditionnels ( &
version a quatre d'entre eux).
Tout indique que la Goyave de l' &
méthode moins efficace que le plus "naturel" &&
version.
... Ou est-il?
Comme indiqué précédemment, je suis en cours d'exécution les exemples ci-dessus avec 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)
Mais ce que si je passe à 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
.....
Surprise! Le code assembleur généré pour l' &
méthode par le compilateur JIT dans Java 7, n'a qu' un saut conditionnel présent, et c'est beaucoup plus court! Alors que l' &&
méthode (vous devrez me faire confiance sur ce coup, je ne veux pas encombrer la fin!) reste à peu près le même, avec ses deux sauts conditionnels et un couple moins d'instructions, tops.
Regarde comme la Goyave, les ingénieurs savaient ce qu'ils faisaient, après tout! (si ils ont essayé d'optimiser Java 7 temps d'exécution, c'est -; -)
Donc, pour revenir à l'OP dernière question:
est-ce l'utilisation de &
(où &&
serait plus normal) une réelle optimisation?
Et à mon humble avis, la réponse est la même, même pour ce (très!) scénario: cela dépend de votre JVM, votre compilateur, votre PROCESSEUR et de votre entrée de données.