Question

Je travaillais sur un projet aujourd'hui et je me suis retrouvé à utiliser Math.Max ​​à plusieurs endroits et des instructions if en ligne à d'autres endroits.Donc, je me demandais si quelqu'un savait lequel était le "meilleur"...ou plutôt quelles sont les vraies différences.

Par exemple, dans ce qui suit, c1 = c2:

Random rand = new Random();
int a = rand.next(0,10000);
int b = rand.next(0,10000);

int c1 = Math.Max(a, b);
int c2 = a>b ? a : b;

Je pose des questions spécifiquement sur C#, mais je suppose que la réponse pourrait être différente selon les langages, même si je ne sais pas lesquels ont des concepts similaires.

Était-ce utile?

La solution

L'une des différences majeures que je remarquerais tout de suite serait pour des raisons de lisibilité, pour autant que je sache, pour des raisons de mise en œuvre/performances, elles seraient presque équivalent.

Math.Max(a,b) est très simple à comprendre, quelles que soient les connaissances préalables en matière de codage.

a>b ? a : b nécessiterait que l'utilisateur ait au moins une certaine connaissance de l'opérateur ternaire.

"En cas de doute, optez pour la lisibilité"

Autres conseils

J'ai pensé que ce serait amusant d'ajouter quelques chiffres à cette discussion, alors j'ai écrit du code pour le profiler.Comme prévu, ils sont presque identiques à toutes fins pratiques.

Le code fait un milliard de boucles (oui, 1 milliard).En soustrayant la surcharge de la boucle, vous obtenez :

  • Math.Max() a mis 0,0044 secondes pour s'exécuter 1 milliard de fois
  • Le processus en ligne a pris 0,0055 seconde pour s'exécuter 1 milliard de fois.

J'ai soustrait la surcharge que j'ai calculée en exécutant une boucle vide 1 milliard de fois, la surcharge était de 1,2 seconde.

Je l'ai exécuté sur un ordinateur portable, Windows 7 64 bits, Intel Core i5 (U470) 1,3 Ghz.Le code a été compilé en mode release et exécuté sans débogueur connecté.

Voici le code :

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;

namespace TestMathMax {
    class Program {
        static int Main(string[] args) {
            var num1 = 10;
            var num2 = 100;
            var maxValue = 0;
            var LoopCount = 1000000000;
            double controlTotalSeconds;
            { 
                var stopwatch = new Stopwatch();
                stopwatch.Start();
                for (var i = 0; i < LoopCount; i++) {
                    // do nothing
                }
                stopwatch.Stop();
                controlTotalSeconds = stopwatch.Elapsed.TotalSeconds;
                Console.WriteLine("Control - Empty Loop - " + controlTotalSeconds + " seconds");
            }
            Console.WriteLine();
            {
                var stopwatch = new Stopwatch();
                stopwatch.Start();
                for (int i = 0; i < LoopCount; i++) {
                    maxValue = Math.Max(num1, num2);
                }
                stopwatch.Stop();
                Console.WriteLine("Math.Max() - " + stopwatch.Elapsed.TotalSeconds + " seconds");
                Console.WriteLine("Relative: " + (stopwatch.Elapsed.TotalSeconds - controlTotalSeconds) + " seconds");
            }
            Console.WriteLine();
            {
                var stopwatch = new Stopwatch();
                stopwatch.Start();
                for (int i = 0; i < LoopCount; i++) {
                    maxValue = num1 > num2 ? num1 : num2;
                }
                stopwatch.Stop();
                Console.WriteLine("Inline Max: " + stopwatch.Elapsed.TotalSeconds + " seconds");
                Console.WriteLine("Relative: " + (stopwatch.Elapsed.TotalSeconds - controlTotalSeconds) + " seconds");
            }

            Console.ReadLine();

            return maxValue;
        }
    }
}

Résultats MISE À JOUR 07/02/2015

Sur un Windows 8.1, Surface 3 Pro, i7 4650U 2,3 GHz a exécuté une application de console en mode de libération sans que le débogueur ne soit attaché.

  • Math.Max() - 0,3194749 secondes
  • Max en ligne :0,3465041 secondes

si la déclaration est considérée comme bénéfique

Résumé

une déclaration de la forme if (a > max) max = a est le moyen le plus rapide de déterminer le maximum d’un ensemble de nombres.Cependant, l’infrastructure de boucle elle-même prend la majeure partie du temps CPU, cette optimisation est donc finalement discutable.

Détails

La réponse de Luisperezphd est intéressante car elle fournit des chiffres, mais je pense que la méthode est imparfaite :le compilateur déplacera très probablement la comparaison hors de la boucle, de sorte que la réponse ne mesure pas ce qu'elle veut mesurer.Cela explique la différence de temps négligeable entre la boucle de contrôle et les boucles de mesure.

Pour éviter cette optimisation de boucle, j'ai ajouté une opération qui dépend de la variable de boucle, à la boucle de contrôle vide ainsi qu'à toutes les boucles de mesure.J'ai simulé le cas d'utilisation courant consistant à trouver le maximum dans une liste de nombres et j'ai utilisé trois ensembles de données :

  • meilleur cas:le premier nombre est le maximum, tous les nombres suivants sont plus petits
  • pire cas:chaque nombre est plus grand que le précédent, donc le maximum change à chaque itération
  • cas moyen :un ensemble de nombres aléatoires

Voir ci-dessous pour le code.

Le résultat m'a plutôt surpris.Sur mon ordinateur portable Core i5 2520M, j'ai obtenu ce qui suit pour 1 milliard d'itérations (le contrôle à vide prenait environ 2,6 secondes dans tous les cas) :

  • max = Math.Max(max, a):2,0 s dans le meilleur des cas / 1,3 s dans le pire des cas / 2,0 s dans le cas moyen
  • max = Math.Max(a, max):1,6 s dans le meilleur des cas / 2,0 s dans le pire des cas / 1,5 s dans le cas moyen
  • max = max > a ? max : a:1,2 s dans le meilleur des cas / 1,2 s dans le pire des cas / 1,2 s dans le cas moyen
  • if (a > max) max = a:0,2 s dans le meilleur des cas / 0,9 s dans le pire des cas / 0,3 s dans le cas moyen

Ainsi, malgré les longs pipelines CPU et les pénalités de branchement qui en résultent, le bon vieux if la déclaration est clairement la gagnante pour tous les ensembles de données simulés ;dans le meilleur des cas, c'est 10 fois plus rapide que Math.Max, et dans le pire des cas, encore plus de 30 % plus rapide.

Une autre surprise est que l'ordre des arguments à Math.Max importe.Vraisemblablement, cela est dû au fait que la logique de prédiction des branches du processeur fonctionne différemment dans les deux cas et qu'elle prédit mal les branches plus ou moins en fonction de l'ordre des arguments.

Cependant, la majorité du temps CPU est dépensée dans l’infrastructure de boucle, donc au final cette optimisation est pour le moins discutable.Il permet une réduction mesurable mais mineure du temps d’exécution global.

MISE À JOUR par luisperezphd

Je ne pouvais pas faire de cela un commentaire et il était plus logique de l'écrire ici plutôt que dans le cadre de ma réponse afin qu'il soit dans son contexte.

Votre théorie est logique, mais je n'ai pas pu reproduire les résultats.Tout d'abord, pour une raison quelconque, en utilisant votre code, ma boucle de contrôle prenait plus de temps que les boucles contenant du travail.

Pour cette raison, j'ai fait les chiffres ici par rapport au temps le plus bas au lieu de la boucle de contrôle.Les secondes dans les résultats indiquent combien de temps cela a pris par rapport au temps le plus rapide.Par exemple, dans les résultats immédiatement en dessous, le temps le plus rapide était pour le meilleur des cas Math.Max(a, max), donc tous les autres résultats représentent le temps qu'ils ont pris par rapport à cela.

Voici les résultats que j'ai obtenus :

  • max = Math.Max(max, a):0,012 s dans le meilleur des cas / 0,007 s dans le pire des cas / 0,028 s dans le cas moyen
  • max = Math.Max(a, max):0,000 meilleur cas / 0,021 pire cas / 0,019 s cas moyen
  • max = max > a ? max : a:0,022 s dans le meilleur des cas / 0,02 s dans le pire des cas / 0,01 s dans le cas moyen
  • if (a > max) max = a:0,015 s dans le meilleur des cas / 0,024 s dans le pire des cas / 0,019 s dans le cas moyen

La deuxième fois que je l'ai exécuté, j'ai obtenu :

  • max = Math.Max(max, a) :0,024 s dans le meilleur des cas / 0,010 s dans le pire des cas / 0,009 s dans le cas moyen
  • max = Math.Max(a, max):0,001 s dans le meilleur des cas / 0,000 s dans le pire des cas / 0,018 s dans le cas moyen
  • max = max > a ? max : a:0,011 s dans le meilleur des cas / 0,005 s dans le pire des cas / 0,018 s dans le cas moyen
  • if (a > max) max = a:0,000 s dans le meilleur des cas / 0,005 s dans le pire des cas / 0,039 s dans le cas moyen

Il y a suffisamment de volume dans ces tests pour que toute anomalie aurait dû être effacée.Malgré cela, les résultats sont assez différents.Peut-être que la grande allocation de mémoire pour le tableau a quelque chose à voir avec cela.Ou peut-être que la différence est si petite que tout ce qui se passe sur l'ordinateur à ce moment-là est la véritable cause de la variation.

Notez que le temps le plus rapide, représenté dans les résultats ci-dessus par 0,000, est d'environ 8 secondes.Donc, si l’on considère que la période la plus longue était alors de 8,039, la variation dans le temps est d’environ un demi pour cent (0,5 %) – c’est-à-dire trop petite pour avoir de l’importance.

L'ordinateur

Le code a été exécuté sur Windows 8.1, i7 4810MQ 2,8 Ghz et compilé en .NET 4.0.

Modifications des codes

J'ai un peu modifié votre code pour afficher les résultats dans le format indiqué ci-dessus.J'ai également ajouté du code supplémentaire pour attendre 1 seconde après le démarrage afin de tenir compte de tout temps de chargement supplémentaire dont .NET pourrait avoir besoin lors de l'exécution de l'assembly.

J'ai également exécuté tous les tests deux fois pour tenir compte des éventuelles optimisations du processeur.Finalement j'ai changé le int pour i à un unit afin que je puisse exécuter la boucle 4 milliards de fois au lieu de 1 milliard pour obtenir une durée plus longue.

C'est probablement exagéré, mais le but est de s'assurer autant que possible que les tests ne sont affectés par aucun de ces facteurs.

Vous pouvez trouver le code à l'adresse suivante : http://pastebin.com/84qi2cbD

Code

using System;
using System.Diagnostics;

namespace ProfileMathMax
{
  class Program
  {
    static double controlTotalSeconds;
    const int InnerLoopCount = 100000;
    const int OuterLoopCount = 1000000000 / InnerLoopCount;
    static int[] values = new int[InnerLoopCount];
    static int total = 0;

    static void ProfileBase()
    {
        Stopwatch stopwatch = new Stopwatch();
        stopwatch.Start();
        int maxValue;
        for (int j = 0; j < OuterLoopCount; j++)
        {
            maxValue = 0;
            for (int i = 0; i < InnerLoopCount; i++)
            {
                // baseline
                total += values[i];
            }
        }
        stopwatch.Stop();
        controlTotalSeconds = stopwatch.Elapsed.TotalSeconds;
        Console.WriteLine("Control - Empty Loop - " + controlTotalSeconds + " seconds");
    }

    static void ProfileMathMax()
    {
        int maxValue;
        Stopwatch stopwatch = new Stopwatch();
        stopwatch.Start();
        for (int j = 0; j < OuterLoopCount; j++)
        {
            maxValue = 0;
            for (int i = 0; i < InnerLoopCount; i++)
            {
                maxValue = Math.Max(values[i], maxValue);
                total += values[i];
            }
        }
        stopwatch.Stop();
        Console.WriteLine("Math.Max(a, max) - " + stopwatch.Elapsed.TotalSeconds + " seconds");
        Console.WriteLine("Relative: " + (stopwatch.Elapsed.TotalSeconds - controlTotalSeconds) + " seconds");
    }

    static void ProfileMathMaxReverse()
    {
        int maxValue;
        Stopwatch stopwatch = new Stopwatch();
        stopwatch.Start();
        for (int j = 0; j < OuterLoopCount; j++)
        {
            maxValue = 0;
            for (int i = 0; i < InnerLoopCount; i++)
            {
                maxValue = Math.Max(maxValue, values[i]);
                total += values[i];
            }
        }
        stopwatch.Stop();
        Console.WriteLine("Math.Max(max, a) - " + stopwatch.Elapsed.TotalSeconds + " seconds");
        Console.WriteLine("Relative: " + (stopwatch.Elapsed.TotalSeconds - controlTotalSeconds) + " seconds");
    }

    static void ProfileInline()
    {
        int maxValue = 0;
        Stopwatch stopwatch = new Stopwatch();
        stopwatch.Start();
        for (int j = 0; j < OuterLoopCount; j++)
        {
            maxValue = 0;
            for (int i = 0; i < InnerLoopCount; i++)
            {
                maxValue = maxValue > values[i] ? values[i] : maxValue;
                total += values[i];
            }
        }
        stopwatch.Stop();
        Console.WriteLine("max = max > a ? a : max: " + stopwatch.Elapsed.TotalSeconds + " seconds");
        Console.WriteLine("Relative: " + (stopwatch.Elapsed.TotalSeconds - controlTotalSeconds) + " seconds");
    }

    static void ProfileIf()
    {
        int maxValue = 0;
        Stopwatch stopwatch = new Stopwatch();
        stopwatch.Start();
        for (int j = 0; j < OuterLoopCount; j++)
        {
            maxValue = 0;
            for (int i = 0; i < InnerLoopCount; i++)
            {
                if (values[i] > maxValue)
                    maxValue = values[i];
                total += values[i];
            }
        }
        stopwatch.Stop();
        Console.WriteLine("if (a > max) max = a: " + stopwatch.Elapsed.TotalSeconds + " seconds");
        Console.WriteLine("Relative: " + (stopwatch.Elapsed.TotalSeconds - controlTotalSeconds) + " seconds");
    }

    static void Main(string[] args)
    {
        Random rnd = new Random();
        for (int i = 0; i < InnerLoopCount; i++)
        {
            //values[i] = i;  // worst case: every new number biggest than the previous
            //values[i] = i == 0 ? 1 : 0;  // best case: first number is the maximum
            values[i] = rnd.Next(int.MaxValue);  // average case: random numbers
        }

        ProfileBase();
        Console.WriteLine();
        ProfileMathMax();
        Console.WriteLine();
        ProfileMathMaxReverse();
        Console.WriteLine();
        ProfileInline();
        Console.WriteLine();
        ProfileIf();
        Console.ReadLine();
    }
  }
}

Je dirais qu'il est plus rapide de comprendre ce que fait Math.Max, et cela devrait vraiment être le seul facteur décisif ici.

Mais par indulgence, il est intéressant de considérer que Math.Max(a,b) évalue les arguments une fois, tandis que a > b ? a : b évalue l’un d’eux deux fois.Ce n'est pas un problème avec les variables locales, mais pour les propriétés ayant des effets secondaires, l'effet secondaire peut se produire deux fois.

Si le JITer choisit d'intégrer la fonction Math.Max, le code exécutable sera identique à l'instruction if.Si Math.Max ​​n'est pas intégré, il s'exécutera comme un appel de fonction avec une surcharge d'appel et de retour non présente dans l'instruction if.Ainsi, l'instruction if donnera des performances identiques à Math.Max() dans le cas inline ou l'instruction if peut être quelques cycles d'horloge plus rapides dans le cas non inline, mais la différence ne sera perceptible que si vous exécutez des dizaines. de millions de comparaisons.

Étant donné que la différence de performances entre les deux est suffisamment petite pour être négligeable dans la plupart des situations, je préférerais Math.Max(a,b) car il est plus facile à lire.

Math.Max(a,b)

est PAS équivalent à a > b ? a : b dans tous les cas.

Math.Max renvoie la plus grande valeur des deux arguments, soit :

if (a == b) return a; // or b, doesn't matter since they're identical
else if (a > b && b < a) return a;
else if (b > a && a < b) return b;
else return undefined;

L'indéfini est mappé à double.NaN en cas de double surcharge de Math.Max Par exemple.

une > b ?un :b

est évalué à a si a est supérieur à b, ce qui ne signifie pas nécessairement que b est inférieur à a.

Un exemple simple qui démontre qu’ils ne sont pas équivalents :

var a = 0.0/0.0; // or double.NaN
var b = 1.0;
a > b ? a : b // evaluates to 1.0
Math.Max(a, b) // returns double.NaN

Concernant les performances, les processeurs modernes disposent d'un pipeline de commandes interne tel que chaque commande d'assemblage est exécutée en plusieurs étapes internes.(par exemple.récupération, interprétation, calcul, stockage)

Dans la plupart des cas, le processeur est suffisamment intelligent pour exécuter ces étapes en parallèle pour des commandes séquentielles, de sorte que le débit global est très élevé.

C'est bien jusqu'à ce qu'il y ait une branche (if, ?: etc.) .La branche peut interrompre la séquence et forcer le processeur à détruire le pipeline.Cela coûte beaucoup de cycles d'horloge.

En théorie, si le compilateur est suffisamment intelligent, le Math.Max peut être implémenté à l'aide d'une commande CPU intégrée et le branchement peut être évité.

Dans ce cas le Math.Max serait en fait plus rapide que le if - mais cela dépend du compilateur.

En cas de Max plus compliqué - comme travailler sur des vecteurs, double []v; v.Max() le compilateur peut utiliser du code de bibliothèque hautement optimisé, qui peut être beaucoup plus rapide que le code compilé classique.

Il est donc préférable d'utiliser Math.Max, mais il est également recommandé de vérifier sur votre système cible et votre compilateur particuliers si cela est suffisamment important.

Faites une opération;N doit être >= 0

Solutions générales :

A) N = Math.Max(0, N)
B) if(N < 0){N = 0}

Tri par vitesse :

Lent:Math.Max ​​(A) < (B) instruction if-then : Rapide (3 % plus rapide que la solution 'A')

Mais ma solution est 4 % plus rapide que la solution « B » :

N *= Math.Sign(1 + Math.Sign(N));
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top