J'ai un algorithme écrit en Java que je voudrais rendre plus efficace. Une partie de ce que je pense pourrait être plus efficace consiste à trouver le plus petit des 3 chiffres. Actuellement, j'utilise le Math.min
méthode comme ci-dessous:
double smallest = Math.min(a, Math.min(b, c));
Est-ce efficace? Serait-il plus efficace de remplacer par if si des énoncés comme ci-dessous:
double smallest;
if (a <= b && a <= c) {
smallest = a;
} else if (b <= c && b <= a) {
smallest = b;
} else {
smallest = c;
}
Ou si un autre moyen est plus efficace
Je me demande si cela vaut la peine de changer ce que j'utilise actuellement?
Toute augmentation de vitesse serait grandement utile
Non, c'est sérieux pas Cela vaut la peine de changer. Le type d’améliorations que vous obtiendrez en manipulant des micro-optimisations comme celle-ci ne vaudra pas la peine. Même le coût d’appel de méthode sera supprimé si la fonction min
est appelée suffisamment.
Si vous rencontrez un problème avec votre algorithme, votre meilleur choix est d’examiner les macro-optimisations (éléments globaux tels que la sélection ou le réglage d’un algorithme). Vous obtiendrez généralement beaucoup de meilleures améliorations de performances. Là.
Et votre commentaire selon lequel enlever Math.pow
_ a donné des améliorations peut être correct, mais c’est parce que c’est une opération relativement coûteuse. Math.min
ne sera même pas proche de cela en termes de coût.
Pour de nombreuses méthodes de type utilitaire, les bibliothèques communes Apache ont des implémentations solides que vous pouvez exploiter ou obtenir des informations supplémentaires. Dans ce cas, il existe une méthode permettant de trouver le plus petit des trois doublons disponible dans org.Apache.commons.lang.math.NumberUtils. Leur mise en œuvre est en réalité presque identique à votre pensée initiale:
public static double min(double a, double b, double c) {
return Math.min(Math.min(a, b), c);
}
double smallest = a;
if (smallest > b) smallest = b;
if (smallest > c) smallest = c;
Pas nécessairement plus rapide que votre code.
Permettez-moi de répéter tout d'abord ce que d'autres ont déjà dit, en citant l'article "Programmation structurée avec instructions" de Donald Knuth:
Nous devrions oublier les petites efficacités, disons environ 97% du temps: une optimisation prématurée est la racine de tout mal.
Pourtant, nous ne devrions pas laisser passer nos opportunités dans ces 3% critiques. Un bon programmeur ne se laissera pas aller à la complaisance par un tel raisonnement, il sera sage de regarder attentivement le code critique; mais seulement après que ce code a été identifié.
(souligné par moi)
Donc, si vous avez identifié qu'une opération apparemment triviale comme le calcul du minimum de trois nombres est le goulet d'étranglement réel (c'est-à-dire le "seuil critique de 3% ") dans votre application, vous pouvez alors l’optimiser.
Et dans ce cas, cela est réellement possible: La méthode Math#min(double,double)
in Java a une sémantique très spéciale:
Retourne la plus petite des deux valeurs doubles. C'est-à-dire que le résultat est la valeur la plus proche de l'infini négatif. Si les arguments ont la même valeur, le résultat est la même valeur. Si l'une ou l'autre valeur est NaN, le résultat est NaN. Contrairement aux opérateurs de comparaison numérique, cette méthode considère que le zéro négatif est strictement inférieur au zéro positif. Si un argument est à zéro positif et l'autre à zéro négatif, le résultat est zéro négatif.
On peut regarder l'implémentation et voir que c'est en fait assez complexe:
public static double min(double a, double b) {
if (a != a)
return a; // a is NaN
if ((a == 0.0d) &&
(b == 0.0d) &&
(Double.doubleToRawLongBits(b) == negativeZeroDoubleBits)) {
// Raw conversion ok since NaN can't map to -0.0.
return b;
}
return (a <= b) ? a : b;
}
Maintenant, il peut être important de souligner que ce comportement est différent à partir d'une simple comparaison. Ceci peut facilement être examiné avec l'exemple suivant:
public class MinExample
{
public static void main(String[] args)
{
test(0.0, 1.0);
test(1.0, 0.0);
test(-0.0, 0.0);
test(Double.NaN, 1.0);
test(1.0, Double.NaN);
}
private static void test(double a, double b)
{
double minA = Math.min(a, b);
double minB = a < b ? a : b;
System.out.println("a: "+a);
System.out.println("b: "+b);
System.out.println("minA "+minA);
System.out.println("minB "+minB);
if (Double.doubleToRawLongBits(minA) !=
Double.doubleToRawLongBits(minB))
{
System.out.println(" -> Different results!");
}
System.out.println();
}
}
Cependant: si le traitement de NaN
et du zéro positif/négatif n’est pas pertinent pour votre application, vous pouvez remplacer la solution basée sur Math.min
Par une solution basée sur une simple comparaison, et voir si cela fait une différence.
Bien entendu, cela dépendra de l'application. Voici un micro-repère artificiel simple ( à prendre avec un grain de sel! )
import Java.util.Random;
public class MinPerformance
{
public static void main(String[] args)
{
bench();
}
private static void bench()
{
int runs = 1000;
for (int size=10000; size<=100000; size+=10000)
{
Random random = new Random(0);
double data[] = new double[size];
for (int i=0; i<size; i++)
{
data[i] = random.nextDouble();
}
benchA(data, runs);
benchB(data, runs);
}
}
private static void benchA(double data[], int runs)
{
long before = System.nanoTime();
double sum = 0;
for (int r=0; r<runs; r++)
{
for (int i=0; i<data.length-3; i++)
{
sum += minA(data[i], data[i+1], data[i+2]);
}
}
long after = System.nanoTime();
System.out.println("A: length "+data.length+", time "+(after-before)/1e6+", result "+sum);
}
private static void benchB(double data[], int runs)
{
long before = System.nanoTime();
double sum = 0;
for (int r=0; r<runs; r++)
{
for (int i=0; i<data.length-3; i++)
{
sum += minB(data[i], data[i+1], data[i+2]);
}
}
long after = System.nanoTime();
System.out.println("B: length "+data.length+", time "+(after-before)/1e6+", result "+sum);
}
private static double minA(double a, double b, double c)
{
return Math.min(a, Math.min(b, c));
}
private static double minB(double a, double b, double c)
{
if (a < b)
{
if (a < c)
{
return a;
}
return c;
}
if (b < c)
{
return b;
}
return c;
}
}
(Avertissement: Microbenchmarking in Java est un art, et pour des résultats plus fiables, envisagez d'utiliser JMH - Calibre ).
L’exécution de cette opération avec JRE 1.8.0_31 peut donner lieu à quelque chose comme:
....
A: length 90000, time 545.929078, result 2.247805342620906E7
B: length 90000, time 441.999193, result 2.247805342620906E7
A: length 100000, time 608.046928, result 2.5032781001456387E7
B: length 100000, time 493.747898, result 2.5032781001456387E7
Ceci au moins suggère qu'il pourrait être possible d'extraire quelques pour cent ici (encore une fois, dans un exemple très artificiel).
En analysant cela plus en profondeur, en regardant la sortie de désassemblage des points chauds créée avec
Java -server -XX:+UnlockDiagnosticVMOptions -XX:+TraceClassLoading -XX:+LogCompilation -XX:+PrintAssembly MinPerformance
on peut voir les versions optimisées des deux méthodes, minA
et minB
.
Tout d’abord, le résultat de la méthode qui utilise Math.min
:
Decoding compiled method 0x0000000002992310:
Code:
[Entry Point]
[Verified Entry Point]
[Constants]
# {method} {0x000000001c010910} 'minA' '(DDD)D' in 'MinPerformance'
# parm0: xmm0:xmm0 = double
# parm1: xmm1:xmm1 = double
# parm2: xmm2:xmm2 = double
# [sp+0x60] (sp of caller)
0x0000000002992480: mov %eax,-0x6000(%rsp)
0x0000000002992487: Push %rbp
0x0000000002992488: sub $0x50,%rsp
0x000000000299248c: movabs $0x1c010cd0,%rsi
0x0000000002992496: mov 0x8(%rsi),%edi
0x0000000002992499: add $0x8,%edi
0x000000000299249c: mov %edi,0x8(%rsi)
0x000000000299249f: movabs $0x1c010908,%rsi ; {metadata({method} {0x000000001c010910} 'minA' '(DDD)D' in 'MinPerformance')}
0x00000000029924a9: and $0x3ff8,%edi
0x00000000029924af: cmp $0x0,%edi
0x00000000029924b2: je 0x00000000029924e8 ;*dload_0
; - MinPerformance::minA@0 (line 58)
0x00000000029924b8: vmovsd %xmm0,0x38(%rsp)
0x00000000029924be: vmovapd %xmm1,%xmm0
0x00000000029924c2: vmovapd %xmm2,%xmm1 ;*invokestatic min
; - MinPerformance::minA@4 (line 58)
0x00000000029924c6: nop
0x00000000029924c7: callq 0x00000000028c6360 ; OopMap{off=76}
;*invokestatic min
; - MinPerformance::minA@4 (line 58)
; {static_call}
0x00000000029924cc: vmovapd %xmm0,%xmm1 ;*invokestatic min
; - MinPerformance::minA@4 (line 58)
0x00000000029924d0: vmovsd 0x38(%rsp),%xmm0 ;*invokestatic min
; - MinPerformance::minA@7 (line 58)
0x00000000029924d6: nop
0x00000000029924d7: callq 0x00000000028c6360 ; OopMap{off=92}
;*invokestatic min
; - MinPerformance::minA@7 (line 58)
; {static_call}
0x00000000029924dc: add $0x50,%rsp
0x00000000029924e0: pop %rbp
0x00000000029924e1: test %eax,-0x27623e7(%rip) # 0x0000000000230100
; {poll_return}
0x00000000029924e7: retq
0x00000000029924e8: mov %rsi,0x8(%rsp)
0x00000000029924ed: movq $0xffffffffffffffff,(%rsp)
0x00000000029924f5: callq 0x000000000297e260 ; OopMap{off=122}
;*synchronization entry
; - MinPerformance::minA@-1 (line 58)
; {runtime_call}
0x00000000029924fa: jmp 0x00000000029924b8
0x00000000029924fc: nop
0x00000000029924fd: nop
0x00000000029924fe: mov 0x298(%r15),%rax
0x0000000002992505: movabs $0x0,%r10
0x000000000299250f: mov %r10,0x298(%r15)
0x0000000002992516: movabs $0x0,%r10
0x0000000002992520: mov %r10,0x2a0(%r15)
0x0000000002992527: add $0x50,%rsp
0x000000000299252b: pop %rbp
0x000000000299252c: jmpq 0x00000000028ec620 ; {runtime_call}
0x0000000002992531: hlt
0x0000000002992532: hlt
0x0000000002992533: hlt
0x0000000002992534: hlt
0x0000000002992535: hlt
0x0000000002992536: hlt
0x0000000002992537: hlt
0x0000000002992538: hlt
0x0000000002992539: hlt
0x000000000299253a: hlt
0x000000000299253b: hlt
0x000000000299253c: hlt
0x000000000299253d: hlt
0x000000000299253e: hlt
0x000000000299253f: hlt
[Stub Code]
0x0000000002992540: nop ; {no_reloc}
0x0000000002992541: nop
0x0000000002992542: nop
0x0000000002992543: nop
0x0000000002992544: nop
0x0000000002992545: movabs $0x0,%rbx ; {static_stub}
0x000000000299254f: jmpq 0x000000000299254f ; {runtime_call}
0x0000000002992554: nop
0x0000000002992555: movabs $0x0,%rbx ; {static_stub}
0x000000000299255f: jmpq 0x000000000299255f ; {runtime_call}
[Exception Handler]
0x0000000002992564: callq 0x000000000297b9e0 ; {runtime_call}
0x0000000002992569: mov %rsp,-0x28(%rsp)
0x000000000299256e: sub $0x80,%rsp
0x0000000002992575: mov %rax,0x78(%rsp)
0x000000000299257a: mov %rcx,0x70(%rsp)
0x000000000299257f: mov %rdx,0x68(%rsp)
0x0000000002992584: mov %rbx,0x60(%rsp)
0x0000000002992589: mov %rbp,0x50(%rsp)
0x000000000299258e: mov %rsi,0x48(%rsp)
0x0000000002992593: mov %rdi,0x40(%rsp)
0x0000000002992598: mov %r8,0x38(%rsp)
0x000000000299259d: mov %r9,0x30(%rsp)
0x00000000029925a2: mov %r10,0x28(%rsp)
0x00000000029925a7: mov %r11,0x20(%rsp)
0x00000000029925ac: mov %r12,0x18(%rsp)
0x00000000029925b1: mov %r13,0x10(%rsp)
0x00000000029925b6: mov %r14,0x8(%rsp)
0x00000000029925bb: mov %r15,(%rsp)
0x00000000029925bf: movabs $0x515db148,%rcx ; {external_Word}
0x00000000029925c9: movabs $0x2992569,%rdx ; {internal_Word}
0x00000000029925d3: mov %rsp,%r8
0x00000000029925d6: and $0xfffffffffffffff0,%rsp
0x00000000029925da: callq 0x00000000512a9020 ; {runtime_call}
0x00000000029925df: hlt
[Deopt Handler Code]
0x00000000029925e0: movabs $0x29925e0,%r10 ; {section_Word}
0x00000000029925ea: Push %r10
0x00000000029925ec: jmpq 0x00000000028c7340 ; {runtime_call}
0x00000000029925f1: hlt
0x00000000029925f2: hlt
0x00000000029925f3: hlt
0x00000000029925f4: hlt
0x00000000029925f5: hlt
0x00000000029925f6: hlt
0x00000000029925f7: hlt
On peut voir que le traitement de cas spéciaux demande un certain effort - comparé au résultat qui utilise des comparaisons simples, ce qui est plutôt simple:
Decoding compiled method 0x0000000002998790:
Code:
[Entry Point]
[Verified Entry Point]
[Constants]
# {method} {0x000000001c0109c0} 'minB' '(DDD)D' in 'MinPerformance'
# parm0: xmm0:xmm0 = double
# parm1: xmm1:xmm1 = double
# parm2: xmm2:xmm2 = double
# [sp+0x20] (sp of caller)
0x00000000029988c0: sub $0x18,%rsp
0x00000000029988c7: mov %rbp,0x10(%rsp) ;*synchronization entry
; - MinPerformance::minB@-1 (line 63)
0x00000000029988cc: vucomisd %xmm0,%xmm1
0x00000000029988d0: ja 0x00000000029988ee ;*ifge
; - MinPerformance::minB@3 (line 63)
0x00000000029988d2: vucomisd %xmm1,%xmm2
0x00000000029988d6: ja 0x00000000029988de ;*ifge
; - MinPerformance::minB@22 (line 71)
0x00000000029988d8: vmovapd %xmm2,%xmm0
0x00000000029988dc: jmp 0x00000000029988e2
0x00000000029988de: vmovapd %xmm1,%xmm0 ;*synchronization entry
; - MinPerformance::minB@-1 (line 63)
0x00000000029988e2: add $0x10,%rsp
0x00000000029988e6: pop %rbp
0x00000000029988e7: test %eax,-0x27688ed(%rip) # 0x0000000000230000
; {poll_return}
0x00000000029988ed: retq
0x00000000029988ee: vucomisd %xmm0,%xmm2
0x00000000029988f2: ja 0x00000000029988e2 ;*ifge
; - MinPerformance::minB@10 (line 65)
0x00000000029988f4: vmovapd %xmm2,%xmm0
0x00000000029988f8: jmp 0x00000000029988e2
0x00000000029988fa: hlt
0x00000000029988fb: hlt
0x00000000029988fc: hlt
0x00000000029988fd: hlt
0x00000000029988fe: hlt
0x00000000029988ff: hlt
[Exception Handler]
[Stub Code]
0x0000000002998900: jmpq 0x00000000028ec920 ; {no_reloc}
[Deopt Handler Code]
0x0000000002998905: callq 0x000000000299890a
0x000000000299890a: subq $0x5,(%rsp)
0x000000000299890f: jmpq 0x00000000028c7340 ; {runtime_call}
0x0000000002998914: hlt
0x0000000002998915: hlt
0x0000000002998916: hlt
0x0000000002998917: hlt
Il est difficile de dire s'il existe ou non des cas où une telle optimisation fait vraiment une différence dans une application. Mais au moins, l’essentiel est:
Math#min(double,double)
est pas identique à une simple comparaison, et le traitement des cas particuliers n'est pas gratuitMath#min
N'est pas nécessaire, puis une approche basée sur la comparaison peut être plus efficacemin(double,double,double)
de toute façon, pour plus de commodité et de lisibilité, et il serait alors facile de faire deux exécutions avec les différentes implémentations, et de voir si cela affecte réellement la performance.(Note latérale: Les méthodes de type intégral, comme Math.min(int,int)
en fait sont une simple comparaison - donc je m'attendrais à non différence pour ceux-ci).
Vous pouvez utiliser l'opérateur ternaire comme suit:
smallest=(a<b)?((a<c)?a:c):((b<c)?b:c);
Ce qui prend seulement une tâche et deux comparaisons minimum.
Mais je pense que ces déclarations n’auraient aucun effet sur le temps d’exécution, votre logique initiale prendra le même temps que le mien et tous les autres.
Le code efficace de l'OP a un bug:
quand a == b
et a (or b) < c
, le code choisira c au lieu de a ou b.
double smallest;
if(a<b && a<c){
smallest = a;
}else if(b<c && b<a){
smallest = b;
}else{
smallest = c;
}
peut être amélioré pour:
double smallest;
if(a<b && a<c){
smallest = a;
}else if(b<c){
smallest = b;
}else{
smallest = c;
}
Si vous appelez min () environ 1kk fois avec différents a, b, c, utilisez ma méthode:
Ici seulement deux comparaisons. Il n'y a aucun moyen de calculer plus vite: P
public static double min(double a, double b, double c) {
if (a > b) { //if true, min = b
if (b > c) { //if true, min = c
return c;
} else { //else min = b
return b;
}
} //else min = a
if (a > c) { // if true, min=c
return c;
} else {
return a;
}
}
Tout semble aller, votre code ira bien, à moins que vous ne le fassiez dans une boucle serrée. Je considérerais aussi
double min;
min = (a<b) ? a : b;
min = (min<c) ? min : c;
Math.min
Utilise une simple comparaison pour faire son travail. Le seul avantage de ne pas utiliser Math.min est de sauvegarder les appels de fonctions supplémentaires, mais il s’agit d’une sauvegarde négligeable.
Si vous avez plus que trois nombres, avoir une méthode minimum
pour un nombre quelconque de double
s pourrait être utile et ressembler à quelque chose comme:
public static double min(double ... numbers) {
double min = numbers[0];
for (int i=1 ; i<numbers.length ; i++) {
min = (min <= numbers[i]) ? min : numbers[i];
}
return min;
}
Pour trois nombres, il s'agit de l'équivalent fonctionnel de Math.min(a, Math.min(b, c));
, mais vous enregistrez une invocation de méthode.
Pour une efficacité optimale des caractères de code, je ne trouve rien de mieux que
smallest = a<b&&a<c?a:b<c?b:c;
Pour ceux qui trouveront ce sujet beaucoup plus tard:
Si vous n'avez que trois valeurs à comparer, il n'y a pas de différence significative. Mais si vous devez trouver min, par exemple, trente ou soixante valeurs, "min" pourrait être plus facile à lire dans le code l'année prochaine:
int smallest;
smallest = min(a1, a2);
smallest = min(smallest, a3);
smallest = min(smallest, a4);
...
smallest = min(smallest, a37);
Mais si vous pensez à la vitesse, peut-être un meilleur moyen serait-il de mettre les valeurs dans la liste, puis de trouver le minimum:
List<Integer> mySet = Arrays.asList(a1, a2, a3, ..., a37);
int smallest = Collections.min(mySet);
Accepteriez-vous?
J'utiliserais min/max
(et ne vous inquiétez pas sinon) ... voici cependant une autre approche "à la main longue" qui peut être ou ne pas être plus facile à comprendre pour certaines personnes. (Je voudrais pas m'attendre à ce que ce soit plus rapide ou plus lent que le code de l'article.)
int smallest;
if (a < b) {
if (a > c) {
smallest = c;
} else { // a <= c
smallest = a;
}
} else { // a >= b
if (b > c) {
smallest = c;
} else { // b <= c
smallest = b;
}
}
Je viens de le jeter dans le mélange.
Notez qu'il ne s'agit que de la variante à effet secondaire de la réponse d'Abhishek.