Je travaille sur du code Java qui doit être hautement optimisé car il fonctionnera dans des fonctions actives invoquées à de nombreux endroits dans la logique de mon programme principal. Une partie de ce code implique la multiplication de double
variables de 10
élevé à un nombre arbitraire non négatif int
exponent
s. Un moyen rapide (modification: mais pas le plus rapide possible, voir Mise à jour 2 ci-dessous) pour obtenir le valeur multipliée est à switch
sur le exponent
:
double multiplyByPowerOfTen(final double d, final int exponent) {
switch (exponent) {
case 0:
return d;
case 1:
return d*10;
case 2:
return d*100;
// ... same pattern
case 9:
return d*1000000000;
case 10:
return d*10000000000L;
// ... same pattern with long literals
case 18:
return d*1000000000000000000L;
default:
throw new ParseException("Unhandled power of ten " + power, 0);
}
}
Les ellipses commentées ci-dessus indiquent que les constantes case
int
continuent à s'incrémenter de 1, il y a donc vraiment 19 case
s dans l'extrait de code ci-dessus. Comme je ne savais pas vraiment si j'avais besoin de tous les pouvoirs de 10 dans les instructions case
10
À 18
, J'ai exécuté quelques microbiores comparant le temps nécessaire pour effectuer 10 millions d'opérations. avec cette instruction switch
contre un switch
avec seulement case
s 0
à travers 9
(avec le exponent
limité à 9 ou moins pour éviter de décomposer le switch
) épuré. J'ai eu le résultat plutôt surprenant (du moins pour moi!) Que les instructions plus longues switch
avec plus de case
s'exécutaient réellement plus rapidement.
Sur une alouette, j’ai essayé d’ajouter encore plus case
s qui ne renvoyait que des valeurs factices, et j’ai trouvé que je pouvais faire basculer le commutateur encore plus vite avec environ 22 à 27 déclarés case
s les cas ne sont jamais réellement touchés tant que le code est en cours d'exécution). (Encore une fois, case
s ont été ajoutés de manière contiguë en incrémentant la constante antérieure case
constante de 1
.) Ces différences de temps d’exécution ne sont pas très significatives: pour un exponent
entre 0
et 10
, l'instruction factice switch
finie 10 millions d'exécutions en 1,49 seconde contre 1,54 seconde pour la version non rembourrée, pour une économie totale de 5ns par exécution. Donc, pas le genre de chose qui rend obsédé par le remplissage d'une instruction switch
vaut la peine d'être fait du point de vue de l'optimisation. Mais je trouve toujours curieux et contre-intuitif qu’un switch
ne devienne pas plus lent (ou au mieux reste constant O (1) temps) pour exécuter en plus case
s y sont ajoutés.
Ce sont les résultats que j'ai obtenus en exécutant diverses limites sur les valeurs exponent
générées aléatoirement. Je n’ai pas inclus les résultats jusqu’à 1
Pour la limite exponent
, mais la forme générale de la courbe reste la même, avec une arête autour de la casse 12-17, et une vallée entre 18-28. Tous les tests ont été exécutés dans JUnitBenchmarks à l'aide de conteneurs partagés pour les valeurs aléatoires afin de garantir des entrées de test identiques. J'ai également exécuté les tests dans l'ordre allant de l'instruction la plus longue switch
à la plus courte, et inversement, pour tenter d'éliminer la possibilité de problèmes de test liés à la commande. J'ai mis mon code de test sur un dépôt github si quelqu'un veut essayer de reproduire ces résultats.
Alors, qu'est-ce qui se passe ici? Quelques aléas de ma construction d'architecture ou de micro-benchmark? Ou est-ce que la plage Java switch
est vraiment un peu plus rapide à exécuter dans la plage 18
À 28
case
qu'elle n'est de 11
à 17
?
github test repo "switch-experience"
UPDATE: J'ai un peu nettoyé la bibliothèque de benchmarking et ajouté un fichier texte dans/results avec une sortie sur une gamme plus étendue de exponent
valeurs. J'ai également ajouté une option dans le code de test pour ne pas lancer un Exception
de default
, mais cela ne semble pas affecter les résultats.
UPDATE 2: Vous avez trouvé une assez bonne discussion sur ce problème depuis 2009 sur le forum xkcd ici: http: //forums.xkcd .com/viewtopic.php? f = 11 & t = 33524 . La discussion de l'OP sur l'utilisation de Array.binarySearch()
m'a donné l'idée d'une implémentation simple, basée sur un tableau, du modèle d'exponentiation ci-dessus. La recherche binaire n'est pas nécessaire car je sais quelles sont les entrées dans le array
. Il semble fonctionner environ trois fois plus rapidement que switch
, bien entendu au détriment d’une partie du flux de contrôle fourni par switch
. Ce code a également été ajouté au dépôt github.
Comme indiqué par l'autre réponse , étant donné que les valeurs de casse sont contiguës (par opposition à rares), le code intermédiaire généré pour vos divers tests utilise une table de commutation (instruction de code intermédiaire tableswitch
).
Cependant, une fois que le JIT commence son travail et compile le bytecode dans Assembly, l'instruction tableswitch
ne donne pas toujours un tableau de pointeurs: parfois, la table de basculement est transformée en une table lookupswitch
. (similaire à un if
/else if
structure).
La décompilation de l'assembly généré par le JIT (hotspot JDK 1.7) montre qu'il utilise une succession de if/else si, lorsqu'il y a 17 cas ou moins, un tableau de pointeurs lorsqu'il y en a plus de 18 (plus efficace).
La raison pour laquelle ce nombre magique de 18 est utilisé semble revenir à la valeur par défaut de l'indicateur MinJumpTableSize
de la JVM (autour de la ligne 352 dans le code).
J'ai soulevé la question sur la liste du compilateur Hotspot et cela semble être un héritage de tests antérieurs . Notez que cette valeur par défaut a été supprimée dans JDK 8 après d'autres analyses comparatives ont été effectuées .
Enfin, lorsque la méthode devient trop longue (> 25 cas dans mes tests), elle n’est plus alignée avec les paramètres par défaut de la machine virtuelle Java - c’est la cause la plus probable de la baisse des performances à ce stade.
Avec 5 cas, le code décompilé ressemble à ceci (notez les instructions cmp/je/jg/jmp, l'assembly de if/goto):
[Verified Entry Point]
# {method} 'multiplyByPowerOfTen' '(DI)D' in 'javaapplication4/Test1'
# parm0: xmm0:xmm0 = double
# parm1: rdx = int
# [sp+0x20] (sp of caller)
0x00000000024f0160: mov DWORD PTR [rsp-0x6000],eax
; {no_reloc}
0x00000000024f0167: Push rbp
0x00000000024f0168: sub rsp,0x10 ;*synchronization entry
; - javaapplication4.Test1::multiplyByPowerOfTen@-1 (line 56)
0x00000000024f016c: cmp edx,0x3
0x00000000024f016f: je 0x00000000024f01c3
0x00000000024f0171: cmp edx,0x3
0x00000000024f0174: jg 0x00000000024f01a5
0x00000000024f0176: cmp edx,0x1
0x00000000024f0179: je 0x00000000024f019b
0x00000000024f017b: cmp edx,0x1
0x00000000024f017e: jg 0x00000000024f0191
0x00000000024f0180: test edx,edx
0x00000000024f0182: je 0x00000000024f01cb
0x00000000024f0184: mov ebp,edx
0x00000000024f0186: mov edx,0x17
0x00000000024f018b: call 0x00000000024c90a0 ; OopMap{off=48}
;*new ; - javaapplication4.Test1::multiplyByPowerOfTen@72 (line 83)
; {runtime_call}
0x00000000024f0190: int3 ;*new ; - javaapplication4.Test1::multiplyByPowerOfTen@72 (line 83)
0x00000000024f0191: mulsd xmm0,QWORD PTR [rip+0xffffffffffffffa7] # 0x00000000024f0140
;*dmul
; - javaapplication4.Test1::multiplyByPowerOfTen@52 (line 62)
; {section_Word}
0x00000000024f0199: jmp 0x00000000024f01cb
0x00000000024f019b: mulsd xmm0,QWORD PTR [rip+0xffffffffffffff8d] # 0x00000000024f0130
;*dmul
; - javaapplication4.Test1::multiplyByPowerOfTen@46 (line 60)
; {section_Word}
0x00000000024f01a3: jmp 0x00000000024f01cb
0x00000000024f01a5: cmp edx,0x5
0x00000000024f01a8: je 0x00000000024f01b9
0x00000000024f01aa: cmp edx,0x5
0x00000000024f01ad: jg 0x00000000024f0184 ;*tableswitch
; - javaapplication4.Test1::multiplyByPowerOfTen@1 (line 56)
0x00000000024f01af: mulsd xmm0,QWORD PTR [rip+0xffffffffffffff81] # 0x00000000024f0138
;*dmul
; - javaapplication4.Test1::multiplyByPowerOfTen@64 (line 66)
; {section_Word}
0x00000000024f01b7: jmp 0x00000000024f01cb
0x00000000024f01b9: mulsd xmm0,QWORD PTR [rip+0xffffffffffffff67] # 0x00000000024f0128
;*dmul
; - javaapplication4.Test1::multiplyByPowerOfTen@70 (line 68)
; {section_Word}
0x00000000024f01c1: jmp 0x00000000024f01cb
0x00000000024f01c3: mulsd xmm0,QWORD PTR [rip+0xffffffffffffff55] # 0x00000000024f0120
;*tableswitch
; - javaapplication4.Test1::multiplyByPowerOfTen@1 (line 56)
; {section_Word}
0x00000000024f01cb: add rsp,0x10
0x00000000024f01cf: pop rbp
0x00000000024f01d0: test DWORD PTR [rip+0xfffffffffdf3fe2a],eax # 0x0000000000430000
; {poll_return}
0x00000000024f01d6: ret
Avec 18 cas, l’Assemblée ressemble à ceci (remarquez le tableau de pointeurs utilisé et supprime la nécessité de toutes les comparaisons: jmp QWORD PTR [r8+r10*1]
saute directement à la bonne multiplication) - c’est la raison probable de l’amélioration des performances:
[Verified Entry Point]
# {method} 'multiplyByPowerOfTen' '(DI)D' in 'javaapplication4/Test1'
# parm0: xmm0:xmm0 = double
# parm1: rdx = int
# [sp+0x20] (sp of caller)
0x000000000287fe20: mov DWORD PTR [rsp-0x6000],eax
; {no_reloc}
0x000000000287fe27: Push rbp
0x000000000287fe28: sub rsp,0x10 ;*synchronization entry
; - javaapplication4.Test1::multiplyByPowerOfTen@-1 (line 56)
0x000000000287fe2c: cmp edx,0x13
0x000000000287fe2f: jae 0x000000000287fe46
0x000000000287fe31: movsxd r10,edx
0x000000000287fe34: shl r10,0x3
0x000000000287fe38: movabs r8,0x287fd70 ; {section_Word}
0x000000000287fe42: jmp QWORD PTR [r8+r10*1] ;*tableswitch
; - javaapplication4.Test1::multiplyByPowerOfTen@1 (line 56)
0x000000000287fe46: mov ebp,edx
0x000000000287fe48: mov edx,0x31
0x000000000287fe4d: xchg ax,ax
0x000000000287fe4f: call 0x00000000028590a0 ; OopMap{off=52}
;*new ; - javaapplication4.Test1::multiplyByPowerOfTen@202 (line 96)
; {runtime_call}
0x000000000287fe54: int3 ;*new ; - javaapplication4.Test1::multiplyByPowerOfTen@202 (line 96)
0x000000000287fe55: mulsd xmm0,QWORD PTR [rip+0xfffffffffffffe8b] # 0x000000000287fce8
;*dmul
; - javaapplication4.Test1::multiplyByPowerOfTen@194 (line 92)
; {section_Word}
0x000000000287fe5d: jmp 0x000000000287ff16
0x000000000287fe62: mulsd xmm0,QWORD PTR [rip+0xfffffffffffffe86] # 0x000000000287fcf0
;*dmul
; - javaapplication4.Test1::multiplyByPowerOfTen@188 (line 90)
; {section_Word}
0x000000000287fe6a: jmp 0x000000000287ff16
0x000000000287fe6f: mulsd xmm0,QWORD PTR [rip+0xfffffffffffffe81] # 0x000000000287fcf8
;*dmul
; - javaapplication4.Test1::multiplyByPowerOfTen@182 (line 88)
; {section_Word}
0x000000000287fe77: jmp 0x000000000287ff16
0x000000000287fe7c: mulsd xmm0,QWORD PTR [rip+0xfffffffffffffe7c] # 0x000000000287fd00
;*dmul
; - javaapplication4.Test1::multiplyByPowerOfTen@176 (line 86)
; {section_Word}
0x000000000287fe84: jmp 0x000000000287ff16
0x000000000287fe89: mulsd xmm0,QWORD PTR [rip+0xfffffffffffffe77] # 0x000000000287fd08
;*dmul
; - javaapplication4.Test1::multiplyByPowerOfTen@170 (line 84)
; {section_Word}
0x000000000287fe91: jmp 0x000000000287ff16
0x000000000287fe96: mulsd xmm0,QWORD PTR [rip+0xfffffffffffffe72] # 0x000000000287fd10
;*dmul
; - javaapplication4.Test1::multiplyByPowerOfTen@164 (line 82)
; {section_Word}
0x000000000287fe9e: jmp 0x000000000287ff16
0x000000000287fea0: mulsd xmm0,QWORD PTR [rip+0xfffffffffffffe70] # 0x000000000287fd18
;*dmul
; - javaapplication4.Test1::multiplyByPowerOfTen@158 (line 80)
; {section_Word}
0x000000000287fea8: jmp 0x000000000287ff16
0x000000000287feaa: mulsd xmm0,QWORD PTR [rip+0xfffffffffffffe6e] # 0x000000000287fd20
;*dmul
; - javaapplication4.Test1::multiplyByPowerOfTen@152 (line 78)
; {section_Word}
0x000000000287feb2: jmp 0x000000000287ff16
0x000000000287feb4: mulsd xmm0,QWORD PTR [rip+0xfffffffffffffe24] # 0x000000000287fce0
;*dmul
; - javaapplication4.Test1::multiplyByPowerOfTen@146 (line 76)
; {section_Word}
0x000000000287febc: jmp 0x000000000287ff16
0x000000000287febe: mulsd xmm0,QWORD PTR [rip+0xfffffffffffffe6a] # 0x000000000287fd30
;*dmul
; - javaapplication4.Test1::multiplyByPowerOfTen@140 (line 74)
; {section_Word}
0x000000000287fec6: jmp 0x000000000287ff16
0x000000000287fec8: mulsd xmm0,QWORD PTR [rip+0xfffffffffffffe68] # 0x000000000287fd38
;*dmul
; - javaapplication4.Test1::multiplyByPowerOfTen@134 (line 72)
; {section_Word}
0x000000000287fed0: jmp 0x000000000287ff16
0x000000000287fed2: mulsd xmm0,QWORD PTR [rip+0xfffffffffffffe66] # 0x000000000287fd40
;*dmul
; - javaapplication4.Test1::multiplyByPowerOfTen@128 (line 70)
; {section_Word}
0x000000000287feda: jmp 0x000000000287ff16
0x000000000287fedc: mulsd xmm0,QWORD PTR [rip+0xfffffffffffffe64] # 0x000000000287fd48
;*dmul
; - javaapplication4.Test1::multiplyByPowerOfTen@122 (line 68)
; {section_Word}
0x000000000287fee4: jmp 0x000000000287ff16
0x000000000287fee6: mulsd xmm0,QWORD PTR [rip+0xfffffffffffffe62] # 0x000000000287fd50
;*dmul
; - javaapplication4.Test1::multiplyByPowerOfTen@116 (line 66)
; {section_Word}
0x000000000287feee: jmp 0x000000000287ff16
0x000000000287fef0: mulsd xmm0,QWORD PTR [rip+0xfffffffffffffe60] # 0x000000000287fd58
;*dmul
; - javaapplication4.Test1::multiplyByPowerOfTen@110 (line 64)
; {section_Word}
0x000000000287fef8: jmp 0x000000000287ff16
0x000000000287fefa: mulsd xmm0,QWORD PTR [rip+0xfffffffffffffe5e] # 0x000000000287fd60
;*dmul
; - javaapplication4.Test1::multiplyByPowerOfTen@104 (line 62)
; {section_Word}
0x000000000287ff02: jmp 0x000000000287ff16
0x000000000287ff04: mulsd xmm0,QWORD PTR [rip+0xfffffffffffffe5c] # 0x000000000287fd68
;*dmul
; - javaapplication4.Test1::multiplyByPowerOfTen@98 (line 60)
; {section_Word}
0x000000000287ff0c: jmp 0x000000000287ff16
0x000000000287ff0e: mulsd xmm0,QWORD PTR [rip+0xfffffffffffffe12] # 0x000000000287fd28
;*tableswitch
; - javaapplication4.Test1::multiplyByPowerOfTen@1 (line 56)
; {section_Word}
0x000000000287ff16: add rsp,0x10
0x000000000287ff1a: pop rbp
0x000000000287ff1b: test DWORD PTR [rip+0xfffffffffd9b00df],eax # 0x0000000000230000
; {poll_return}
0x000000000287ff21: ret
Enfin, l’Assemblée avec 30 cas (ci-dessous) ressemble à 18 cas, à l’exception du cas supplémentaire movapd xmm0,xmm1
qui apparaît vers le milieu du code, comme indiqué par @cHao - Cependant, la raison la plus probable de la baisse des performances est que la méthode est trop longue pour être alignée avec les paramètres JVM par défaut:
[Verified Entry Point]
# {method} 'multiplyByPowerOfTen' '(DI)D' in 'javaapplication4/Test1'
# parm0: xmm0:xmm0 = double
# parm1: rdx = int
# [sp+0x20] (sp of caller)
0x0000000002524560: mov DWORD PTR [rsp-0x6000],eax
; {no_reloc}
0x0000000002524567: Push rbp
0x0000000002524568: sub rsp,0x10 ;*synchronization entry
; - javaapplication4.Test1::multiplyByPowerOfTen@-1 (line 56)
0x000000000252456c: movapd xmm1,xmm0
0x0000000002524570: cmp edx,0x1f
0x0000000002524573: jae 0x0000000002524592 ;*tableswitch
; - javaapplication4.Test1::multiplyByPowerOfTen@1 (line 56)
0x0000000002524575: movsxd r10,edx
0x0000000002524578: shl r10,0x3
0x000000000252457c: mulsd xmm0,QWORD PTR [rip+0xfffffffffffffe3c] # 0x00000000025243c0
;*dmul
; - javaapplication4.Test1::multiplyByPowerOfTen@364 (line 118)
; {section_Word}
0x0000000002524584: movabs r8,0x2524450 ; {section_Word}
0x000000000252458e: jmp QWORD PTR [r8+r10*1] ;*tableswitch
; - javaapplication4.Test1::multiplyByPowerOfTen@1 (line 56)
0x0000000002524592: mov ebp,edx
0x0000000002524594: mov edx,0x31
0x0000000002524599: xchg ax,ax
0x000000000252459b: call 0x00000000024f90a0 ; OopMap{off=64}
;*new ; - javaapplication4.Test1::multiplyByPowerOfTen@370 (line 120)
; {runtime_call}
0x00000000025245a0: int3 ;*new ; - javaapplication4.Test1::multiplyByPowerOfTen@370 (line 120)
0x00000000025245a1: mulsd xmm0,QWORD PTR [rip+0xfffffffffffffe27] # 0x00000000025243d0
;*dmul
; - javaapplication4.Test1::multiplyByPowerOfTen@358 (line 116)
; {section_Word}
0x00000000025245a9: jmp 0x0000000002524744
0x00000000025245ae: mulsd xmm0,QWORD PTR [rip+0xfffffffffffffe22] # 0x00000000025243d8
;*dmul
; - javaapplication4.Test1::multiplyByPowerOfTen@348 (line 114)
; {section_Word}
0x00000000025245b6: jmp 0x0000000002524744
0x00000000025245bb: mulsd xmm0,QWORD PTR [rip+0xfffffffffffffe1d] # 0x00000000025243e0
;*dmul
; - javaapplication4.Test1::multiplyByPowerOfTen@338 (line 112)
; {section_Word}
0x00000000025245c3: jmp 0x0000000002524744
0x00000000025245c8: mulsd xmm0,QWORD PTR [rip+0xfffffffffffffe18] # 0x00000000025243e8
;*dmul
; - javaapplication4.Test1::multiplyByPowerOfTen@328 (line 110)
; {section_Word}
0x00000000025245d0: jmp 0x0000000002524744
0x00000000025245d5: mulsd xmm0,QWORD PTR [rip+0xfffffffffffffe13] # 0x00000000025243f0
;*dmul
; - javaapplication4.Test1::multiplyByPowerOfTen@318 (line 108)
; {section_Word}
0x00000000025245dd: jmp 0x0000000002524744
0x00000000025245e2: mulsd xmm0,QWORD PTR [rip+0xfffffffffffffe0e] # 0x00000000025243f8
;*dmul
; - javaapplication4.Test1::multiplyByPowerOfTen@308 (line 106)
; {section_Word}
0x00000000025245ea: jmp 0x0000000002524744
0x00000000025245ef: mulsd xmm0,QWORD PTR [rip+0xfffffffffffffe09] # 0x0000000002524400
;*dmul
; - javaapplication4.Test1::multiplyByPowerOfTen@298 (line 104)
; {section_Word}
0x00000000025245f7: jmp 0x0000000002524744
0x00000000025245fc: mulsd xmm0,QWORD PTR [rip+0xfffffffffffffe04] # 0x0000000002524408
;*dmul
; - javaapplication4.Test1::multiplyByPowerOfTen@288 (line 102)
; {section_Word}
0x0000000002524604: jmp 0x0000000002524744
0x0000000002524609: mulsd xmm0,QWORD PTR [rip+0xfffffffffffffdff] # 0x0000000002524410
;*dmul
; - javaapplication4.Test1::multiplyByPowerOfTen@278 (line 100)
; {section_Word}
0x0000000002524611: jmp 0x0000000002524744
0x0000000002524616: mulsd xmm0,QWORD PTR [rip+0xfffffffffffffdfa] # 0x0000000002524418
;*dmul
; - javaapplication4.Test1::multiplyByPowerOfTen@268 (line 98)
; {section_Word}
0x000000000252461e: jmp 0x0000000002524744
0x0000000002524623: mulsd xmm0,QWORD PTR [rip+0xfffffffffffffd9d] # 0x00000000025243c8
;*dmul
; - javaapplication4.Test1::multiplyByPowerOfTen@258 (line 96)
; {section_Word}
0x000000000252462b: jmp 0x0000000002524744
0x0000000002524630: movapd xmm0,xmm1
0x0000000002524634: mulsd xmm0,QWORD PTR [rip+0xfffffffffffffe0c] # 0x0000000002524448
;*dmul
; - javaapplication4.Test1::multiplyByPowerOfTen@242 (line 92)
; {section_Word}
0x000000000252463c: jmp 0x0000000002524744
0x0000000002524641: movapd xmm0,xmm1
0x0000000002524645: mulsd xmm0,QWORD PTR [rip+0xfffffffffffffddb] # 0x0000000002524428
;*dmul
; - javaapplication4.Test1::multiplyByPowerOfTen@236 (line 90)
; {section_Word}
0x000000000252464d: jmp 0x0000000002524744
0x0000000002524652: movapd xmm0,xmm1
0x0000000002524656: mulsd xmm0,QWORD PTR [rip+0xfffffffffffffdd2] # 0x0000000002524430
;*dmul
; - javaapplication4.Test1::multiplyByPowerOfTen@230 (line 88)
; {section_Word}
0x000000000252465e: jmp 0x0000000002524744
0x0000000002524663: movapd xmm0,xmm1
0x0000000002524667: mulsd xmm0,QWORD PTR [rip+0xfffffffffffffdc9] # 0x0000000002524438
;*dmul
; - javaapplication4.Test1::multiplyByPowerOfTen@224 (line 86)
; {section_Word}
[etc.]
0x0000000002524744: add rsp,0x10
0x0000000002524748: pop rbp
0x0000000002524749: test DWORD PTR [rip+0xfffffffffde1b8b1],eax # 0x0000000000340000
; {poll_return}
0x000000000252474f: ret
Switch - case est plus rapide si les valeurs de case sont placées dans une plage étroite, par exemple.
case 1:
case 2:
case 3:
..
..
case n:
En effet, dans ce cas, le compilateur peut éviter d'effectuer une comparaison pour chaque étape de la requête dans l'instruction switch. Le compilateur crée une table de saut contenant les adresses des actions à effectuer sur différents segments. La valeur sur laquelle le commutateur est exécuté est manipulée pour la convertir en un index dans le jump table
. Dans cette implémentation, le temps pris dans l'instruction switch est bien inférieur au temps pris dans une cascade d'instruction if-else-if équivalente. De plus, le temps pris dans l'instruction switch est indépendant du nombre d'étapes de cas dans l'instruction switch.
Comme indiqué dans wikipedia à propos de instruction switch dans la section Compilation.
Si la plage de valeurs d'entrée est identiquement "petite" et ne comporte que quelques espaces, certains compilateurs intégrant un optimiseur peuvent en réalité implémenter l'instruction switch comme une table de branche ou un tableau de pointeurs de fonction indexés au lieu d'une longue série d'instructions conditionnelles. Cela permet à l’instruction switch de déterminer instantanément quelle branche exécuter sans avoir à passer par une liste de comparaisons.
La réponse se trouve dans le bytecode:
SwitchTest10.Java
public class SwitchTest10 {
public static void main(String[] args) {
int n = 0;
switcher(n);
}
public static void switcher(int n) {
switch(n) {
case 0: System.out.println(0);
break;
case 1: System.out.println(1);
break;
case 2: System.out.println(2);
break;
case 3: System.out.println(3);
break;
case 4: System.out.println(4);
break;
case 5: System.out.println(5);
break;
case 6: System.out.println(6);
break;
case 7: System.out.println(7);
break;
case 8: System.out.println(8);
break;
case 9: System.out.println(9);
break;
case 10: System.out.println(10);
break;
default: System.out.println("test");
}
}
}
Bytecode correspondant; uniquement les parties pertinentes montrées:
public static void switcher(int);
Code:
0: iload_0
1: tableswitch{ //0 to 10
0: 60;
1: 70;
2: 80;
3: 90;
4: 100;
5: 110;
6: 120;
7: 131;
8: 142;
9: 153;
10: 164;
default: 175 }
SwitchTest22.Java:
public class SwitchTest22 {
public static void main(String[] args) {
int n = 0;
switcher(n);
}
public static void switcher(int n) {
switch(n) {
case 0: System.out.println(0);
break;
case 1: System.out.println(1);
break;
case 2: System.out.println(2);
break;
case 3: System.out.println(3);
break;
case 4: System.out.println(4);
break;
case 5: System.out.println(5);
break;
case 6: System.out.println(6);
break;
case 7: System.out.println(7);
break;
case 8: System.out.println(8);
break;
case 9: System.out.println(9);
break;
case 100: System.out.println(10);
break;
case 110: System.out.println(10);
break;
case 120: System.out.println(10);
break;
case 130: System.out.println(10);
break;
case 140: System.out.println(10);
break;
case 150: System.out.println(10);
break;
case 160: System.out.println(10);
break;
case 170: System.out.println(10);
break;
case 180: System.out.println(10);
break;
case 190: System.out.println(10);
break;
case 200: System.out.println(10);
break;
case 210: System.out.println(10);
break;
case 220: System.out.println(10);
break;
default: System.out.println("test");
}
}
}
Bytecode correspondant; encore une fois, seules les parties pertinentes sont affichées:
public static void switcher(int);
Code:
0: iload_0
1: lookupswitch{ //23
0: 196;
1: 206;
2: 216;
3: 226;
4: 236;
5: 246;
6: 256;
7: 267;
8: 278;
9: 289;
100: 300;
110: 311;
120: 322;
130: 333;
140: 344;
150: 355;
160: 366;
170: 377;
180: 388;
190: 399;
200: 410;
210: 421;
220: 432;
default: 443 }
Dans le premier cas, avec des plages étroites, le bytecode compilé utilise un tableswitch
. Dans le second cas, le bytecode compilé utilise un lookupswitch
.
Dans tableswitch
, la valeur entière située en haut de la pile sert à indexer dans la table pour rechercher la branche/cible de saut. Ce saut/branche est alors effectué immédiatement. Il s’agit donc d’une opération O(1)
.
Un lookupswitch
est plus compliqué. Dans ce cas, la valeur entière doit être comparée à toutes les clés de la table jusqu'à ce que la clé correcte soit trouvée. Une fois la clé trouvée, la cible de branche/saut (vers laquelle cette clé est mappée) est utilisée pour le saut. La table utilisée dans lookupswitch
est triée et un algorithme de recherche binaire peut être utilisé pour trouver la clé correcte. Les performances pour une recherche binaire sont O(log n)
, et l'ensemble du processus est également O(log n)
, car le saut est toujours O(1)
. La raison pour laquelle les performances sont inférieures dans le cas de plages fragmentées est que la clé correcte doit d'abord être recherchée, car vous ne pouvez pas indexer directement dans la table.
S'il existe des valeurs fragmentées et que vous ne devez utiliser que tableswitch
, la table contiendra essentiellement des entrées factices pointant vers l'option default
. Par exemple, en supposant que la dernière entrée dans SwitchTest10.Java
Était 21
Au lieu de 10
, Vous obtenez:
public static void switcher(int);
Code:
0: iload_0
1: tableswitch{ //0 to 21
0: 104;
1: 114;
2: 124;
3: 134;
4: 144;
5: 154;
6: 164;
7: 175;
8: 186;
9: 197;
10: 219;
11: 219;
12: 219;
13: 219;
14: 219;
15: 219;
16: 219;
17: 219;
18: 219;
19: 219;
20: 219;
21: 208;
default: 219 }
Donc, le compilateur crée essentiellement cette énorme table contenant des entrées factices entre les espaces, pointant vers la cible de branche de l'instruction default
. Même s'il n'y a pas de default
, il contiendra des entrées pointant vers l'instruction après le bloc de commutation. J'ai fait quelques tests de base et j'ai constaté que si l'écart entre le dernier index et le précédent (9
) Est supérieur à 35
, Il utilise un lookupswitch
à la place de un tableswitch
.
Le comportement de l'instruction switch
est défini dans Spécification de la machine virtuelle Java (§3.10) :
Lorsque les cas de commutation sont rares, la représentation sous forme de table de l'instruction tableswitch devient inefficace en termes d'espace. L'instruction lookupswitch peut être utilisée à la place. L'instruction lookupswitch associe des clés int (les valeurs des libellés de casse) à des décalages cibles dans une table. Lorsqu'une instruction lookupswitch est exécutée, la valeur de l'expression du commutateur est comparée aux clés de la table. Si l'une des clés correspond à la valeur de l'expression, l'exécution se poursuit au décalage cible associé. Si aucune clé ne correspond, l'exécution continue à la cible par défaut. [...]
Puisque la question a déjà été répondue (plus ou moins), voici quelques astuces. Utilisation
private static final double[] mul={1d, 10d...};
static double multiplyByPowerOfTen(final double d, final int exponent) {
if (exponent<0 || exponent>=mul.length) throw new ParseException();//or just leave the IOOBE be
return mul[exponent]*d;
}
Ce code utilise beaucoup moins de IC (cache d'instruction) et sera toujours en ligne. La matrice sera dans le cache de données L1 si le code est chaud. La table de consultation est presque toujours une victoire. (surtout sur les micro-points de repère: D)
Edit: si vous souhaitez que la méthode soit intégrée à chaud, considérez que les chemins non rapides tels que throw new ParseException()
soient aussi courts que le minimum ou déplacez-les dans une méthode statique séparée (en les rendant ainsi au minimum). C’est throw new ParseException("Unhandled power of ten " + power, 0);
est une idée faible, car il absorbe une grande partie du budget en ligne du code qui peut être simplement interprété - la concaténation de chaînes est assez détaillée en bytecode. Plus d'infos et un cas réel avec ArrayList