71 votes

En Java, peut & être plus rapide que &&?

Dans ce code:

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

lors de l' value >= x et value <= y sont plus susceptibles vrai faux sans motif particulier, en utilisant l' & opérateur d'être plus rapide que d'utiliser &&?

Je pense en particulier à propos de la façon dont && paresseusement évalue la droite de l'expression (c'est à dire que si la LHS est vrai), ce qui implique une conditionnelle, alors qu'en Java & dans ce contexte, les garanties évaluation stricte des deux (boolean) sous-expressions. La valeur du résultat est le même de toute façon.

Mais tandis qu'un >= ou <= opérateur d'utiliser une simple comparaison de l'instruction, de l' && doit impliquer une branche, et la branche est sensible à la branche de prédiction de l'échec - que par cette Très Célèbre Question: Pourquoi est-il plus rapide de traiter un tableau trié qu'un tableau non trié?

Donc, forçant l'expression ne pas avoir de paresseux composants sera sûrement plus déterministe et ne pas être vulnérable à la prédiction de l'échec. Droit?

Notes:

  • évidemment la réponse à ma question ne serait Pas si le code ressemble à ceci: if(value >= x && verySlowFunction()). Je me concentre sur "assez simple" RHS expressions.
  • il y a une branche conditionnelle-il de toute façon ( if déclaration). Je n'arrive pas à me prouver que c'est sans importance, et que d'autres formulations peut-être mieux d'exemples, comme boolean b = value >= x && value <= y;
  • ceci s'inscrit dans le monde d'une terrible micro-optimisations. Ouais, je sais :-) ... intéressants?

Mise à jour Juste pour expliquer pourquoi je suis intéressé: j'ai été regarder les systèmes de Martin Thompson a été écrit à ce sujet sur sa Sympathie Mécanique blog, après il est venu et a fait un exposé sur Aeron. L'un des messages clés, c'est que notre matériel est tout ce magique des choses, et nous les développeurs de logiciels tragiquement ne parviennent pas à en profiter. Ne vous inquiétez pas, je ne suis pas sur d'aller s/&&/\&/ sur tout mon code :-) ... mais il y a un certain nombre de questions sur ce site sur l'amélioration de la direction de la prévision par la suppression des branches, et il m'est apparu que la condition des opérateurs booléens sont à la base de conditions de test.

Bien sûr, @StephenC rend le fantastique point que le cintrage de votre code dans des formes bizarres peuvent le rendre moins facile pour les équipes communes d'enquête à la place des optimisations courantes - si pas maintenant, alors dans le futur. Et que le Très Célèbre Question mentionnée ci-dessus est spécial parce qu'il pousse la prédiction de la complexité bien au-delà de l'optimisation des pratiques.

Je suis bien conscient du fait que dans la plupart des (ou presque tous) les situations, && est la plus claire, la plus simple, la plus rapide, la meilleure chose à faire - même si je suis très reconnaissant envers les gens qui ont posté des réponses témoigne de ce! Je suis vraiment curieux de voir si il y a effectivement des cas de quelqu'un d'expérience où la réponse à "Peut - & être plus rapide?" peut-être Oui...

Mise à jour 2: (S'adressant à l'avis que la question est trop large. Je ne veux pas faire de modifications majeures à cette question, car cela pourrait compromettre certaines réponses ci-dessous, qui sont d'une qualité exceptionnelle!) Peut-être un exemple dans la nature, est appelé; c'est à partir de la Goyave LongMath classe (un énorme merci à @maaartinus pour trouver ce):

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

Voir que la première &? Et si vous consultez le lien, la prochaine méthode est appelée lessThanBranchFree(...), ce qui laisse présager que nous sommes dans une branche d'évitement du territoire et de la Goyave est vraiment largement utilisé: chaque cycle enregistré causes du niveau de la mer à la baisse de manière visible. Donc, il faut mettre la question de cette façon: est-ce l'utilisation de & (où && serait plus normal) une réelle optimisation?

74voto

Luis G. Points 3247

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:

  1. 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.
  2. 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.
  3. Ce qui arrive, return true dans le cas où aucun des deux sauts ont été effectués.
  4. 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!

  1. 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.
  2. 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.
  3. 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 values 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.

23voto

SubOptimal Points 15690

Pour ce genre de questions, vous devez exécuter une microbenchmark. J'ai utilisé JMH pour ce test.

Les repères sont mis en œuvre comme

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

et

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

et

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

Avec des valeurs de value, x and y selon l'indice de référence nom.

Le résultat (cinq de chauffe et dix de mesure des itérations) pour le débit de l'analyse comparative est:

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

Le résultat n'est pas différent pour l'évaluation elle-même. Tant pas de perfomance impact est repéré sur le morceau de code je ne voudrais pas essayer de l'optimiser. En fonction de l'endroit dans le code hotspot compilateur peut se décident à faire de l'optimisation. Ce qui, probablement, n'est pas couvert par les repères ci-dessus.

quelques références:

logique booléenne ET - la valeur du résultat est true si les deux opérandes de l'valeurs sont true; sinon, le résultat est false
conditionnel ET - est comme &, mais évalue sa droite opérande seulement si la valeur de sa main gauche opérande est - true
au niveau du bit OU - la valeur du résultat est la inclusif bit à bit OU de l'opérande valeurs

13voto

Stephen C Points 255558

Je vais venir à ce à partir d'un angle différent.

Tenir compte de ces deux fragments de code,

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

et

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

Si nous supposons que value, x, y ont un type primitif, ces deux (partielle) des déclarations donnent le même résultat pour toutes les valeurs d'entrée possibles. (Si wrapper types sont impliqués, alors qu'ils ne sont pas exactement équivalents à cause d'un implicite null test pour y qui pourrait échouer & version et pas l' && version.)

Si le compilateur JIT est en train de faire un bon travail, son optimiseur seront en mesure d'en déduire que ces deux déclarations de faire la même chose:

  • Si l'on est, de manière prévisible, plus vite que les autres, alors il devrait être en mesure d'utiliser la version plus rapide ... dans le JIT code compilé.

  • Si non, alors il n'importe pas quelle version est utilisée au niveau du code source.

  • Depuis le compilateur JIT rassemble chemin des statistiques, avant la compilation, il peut potentiellement avoir plus d'informations sur l'exécution caractéristiques que le programmeur(!).

  • Si la génération actuelle compilateur JIT (sur n'importe quelle plate-forme donnée) n'optimisent pas assez bien pour gérer cela, la prochaine génération pourrait bien le faire ... en fonction de si oui ou non l'observation empirique montre que cela soit la peine de patron pour optimiser.

  • En effet, si vous vous écrire du code Java dans une manière qui optimise pour cela, il y a une chance que par la cueillette la plus "obscur" de la version du code, vous pouvez inhiber l'actuel ou futur compilateur JIT de la capacité à l'optimiser.

En bref, je ne pense pas que vous devriez faire ce genre de micro-optimisation au niveau du code source. Et si vous accepter cet argument1, et de le suivre jusqu'à sa conclusion logique, la question de savoir quelle version est plus rapide, c'est ... discutable2.

1 - je ne prétendons pas que c'est n'importe où près d'être une preuve.

2 - Sauf si vous êtes l'un de la petite communauté de gens qui écrivent des compilateurs Java JIT ...


Le "Très Fameuse Question" est intéressante à deux égards:

  • D'une part, c'est un exemple où le type d'optimisation nécessaires pour faire une différence au delà de la capacité d'un compilateur JIT.

  • D'autre part, il ne serait pas nécessairement la bonne chose pour trier le tableau ... tout simplement parce que un tableau trié peut être traitée plus rapidement. Le coût de trier le tableau, pourrait bien être (beaucoup) plus grand que le fait de gagner.

6voto

Luke Melaia Points 1325

En utilisant soit & ou && requiert tout de même une condition à évaluer de sorte qu'il est peu probable que cela permettra d'économiser tout le temps de traitement - il pourrait même y ajouter, vu que vous êtes en évaluant à la fois les expressions quand vous avez seulement besoin d'évaluer.

À l'aide de & sur && pour enregistrer une nanoseconde si que dans quelques rares situations, c'est inutile, vous avez déjà perdu plus de temps à contempler la différence que vous avez enregistré à l'aide de & sur &&.

Modifier

Je suis curieuse et a décidé de lancer quelques points de repère.

J'ai fait cette classe:

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){

        }
    }
}

et de couru quelques tests de profilage avec NetBeans. Je n'ai pas utilisé toutes les instructions d'impression, de réduire les temps de traitement, il suffit de savoir à la fois d'évaluer la true.

Premier test:

The first profiling test

Deuxième test:

The second profiling test

Troisième test:

The third profiling test

Comme vous pouvez le voir par les tests de profilage, en utilisant un seul & prend 2 à 3 fois plus de temps par rapport à l'aide de deux &&. Cela ne grève, tel qu'un peu étrange comme je l'ai fait s'attendre à de meilleures performances à partir d'un seul &.

Je ne suis pas sûr à 100% pourquoi. Dans les deux cas, les deux expressions doivent être évalués parce que les deux sont vrais. Je soupçonne que la JVM ne certains d'optimisation particulière derrière les coulisses de l'accélérer.

Morale de l'histoire: la convention est bonne et l'optimisation prématurée est mauvais.


Edit 2

J'ai refait le test de code avec @SvetlinZarev commentaires à l'esprit, et quelques autres améliorations. Voici la modification de code de référence:

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

Et voici les tests de performance:

Test 1:

enter image description here

Test 2:

enter image description here

Test 3:

enter image description here

Il prend en compte les différentes valeurs et d'autres conditions.

À l'aide d'un & prend plus de temps à s'exécuter lorsque deux conditions sont remplies, environ 60% ou 2 millisecondes plus de temps. Lorsque l'une ou les deux conditions sont fausses, puis un & court plus vite, mais il ne fonctionne que sur 0.30-0.50 millisecondes plus rapide. Donc, & courir plus vite que && dans la plupart des cas, mais la différence de performance est encore négligeable.

3voto

Joop Eggen Points 30166

Ce que vous recherchez ressemble à ceci:

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

Intéressant, on aimerait presque regarder le code d'octet. Mais difficile à dire. Je souhaite que ce soit une question C.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X