web-dev-qa-db-fra.com

Qu'est-ce qui est plus rapide, allumer chaîne ou elseif sur type?

Disons que j'ai la possibilité d'identifier un chemin de code à prendre sur la base d'une comparaison de chaîne ou bien de choisir le type:

Lequel est plus rapide et pourquoi?

switch(childNode.Name)
{
    case "Bob":
      break;
    case "Jill":
      break;
    case "Marko":
      break;
}

if(childNode is Bob)
{
}
elseif(childNode is Jill)
{
}
else if(childNode is Marko)
{
}

Mise à jour: La raison principale pour laquelle je pose cette question est parce que la déclaration de commutateur est très claire sur ce qui compte comme cas. Par exemple, cela ne vous autorisera pas à utiliser des variables, uniquement des constantes qui sont déplacées vers l'assembly principal. J'ai supposé qu'il y avait cette restriction en raison de certaines choses géniales qu'il faisait. S'il ne s'agit que de traduire en elseifs (comme l'a commenté une affiche), pourquoi ne sommes-nous pas autorisés à utiliser des variables dans les instructions case?

Avertissement: Je suis en post-optimisation. Cette méthode s'appelle plusieurs fois dans une partie lente de l'application.

72
Quibblesome

Les résultats du profil de Greg sont parfaits pour le scénario exact qu'il a couvert, mais il est intéressant de noter que les coûts relatifs des différentes méthodes changent radicalement lorsque l'on considère un certain nombre de facteurs différents, notamment le nombre de types comparés, la fréquence relative et toute tendance dans les données sous-jacentes. .

La réponse simple est que personne ne peut vous dire quelle sera la différence de performance dans votre scénario spécifique. Vous devrez donc mesurer la performance de différentes manières dans votre propre système pour obtenir une réponse précise.

La chaîne If/Else est une approche efficace pour un petit nombre de comparaisons de types, ou si vous pouvez prédire de manière fiable quels sont les quelques types qui constitueront la majorité de ceux que vous voyez. Le problème potentiel de cette approche est qu’au fur et à mesure que le nombre de types augmente, le nombre de comparaisons à exécuter augmente également.

si j'exécute ce qui suit:

int value = 25124;
if(value == 0) ...
else if (value == 1) ...
else if (value == 2) ...
...
else if (value == 25124) ... 

chacune des conditions if précédentes doit être évaluée avant que le bloc correct ne soit entré. D'autre part

switch(value) {
 case 0:...break;
 case 1:...break;
 case 2:...break;
 ...
 case 25124:...break;
}

effectuera un simple saut vers le bon bit de code.

Ce qui est plus compliqué dans votre exemple, c’est que votre autre méthode utilise un commutateur de chaînes plutôt que des entiers, ce qui devient un peu plus compliqué. À un niveau bas, les chaînes ne peuvent pas être activées de la même manière que les valeurs entières, le compilateur C # fait donc de la magie pour que cela fonctionne pour vous. 

Si l'instruction switch est "assez petite" (où le compilateur fait ce qu'il pense être le meilleur automatiquement), le fait d'activer des chaînes génère un code identique à celui d'une chaîne if/else.

switch(someString) {
    case "Foo": DoFoo(); break;
    case "Bar": DoBar(); break;
    default: DoOther; break;
}

est le même que:

if(someString == "Foo") {
    DoFoo();
} else if(someString == "Bar") {
    DoBar();
} else {
    DoOther();
}

Une fois que la liste d'éléments dans le dictionnaire devient "assez grande", le compilateur créera automatiquement un dictionnaire interne qui mappera les chaînes du commutateur sur un index entier, puis sur un commutateur basé sur cet index.

Cela ressemble à quelque chose comme ça (Imaginez plus d'entrées que je ne vais pas écrire)

Un champ statique est défini dans un emplacement "masqué" associé à la classe contenant l'instruction de commutateur de type Dictionary<string, int> et portant un nom mutilé. 

//Make sure the dictionary is loaded
if(theDictionary == null) { 
    //This is simplified for clarity, the actual implementation is more complex 
    // in order to ensure thread safety
    theDictionary = new Dictionary<string,int>();
    theDictionary["Foo"] = 0;
    theDictionary["Bar"] = 1;
}

int switchIndex;
if(theDictionary.TryGetValue(someString, out switchIndex)) {
    switch(switchIndex) {
    case 0: DoFoo(); break;
    case 1: DoBar(); break;
    }
} else {
    DoOther();
}

Dans certains tests rapides que je viens d'exécuter, la méthode If/Else est environ 3 fois plus rapide que le commutateur pour 3 types différents (où les types sont distribués de manière aléatoire). À 25 types, le commutateur est très rapide (16%) à 50 types, il est plus de deux fois plus rapide.

Si vous allez basculer sur un grand nombre de types, je suggérerais une 3ème méthode:

private delegate void NodeHandler(ChildNode node);

static Dictionary<RuntimeTypeHandle, NodeHandler> TypeHandleSwitcher = CreateSwitcher();

private static Dictionary<RuntimeTypeHandle, NodeHandler> CreateSwitcher()
{
    var ret = new Dictionary<RuntimeTypeHandle, NodeHandler>();

    ret[typeof(Bob).TypeHandle] = HandleBob;
    ret[typeof(Jill).TypeHandle] = HandleJill;
    ret[typeof(Marko).TypeHandle] = HandleMarko;

    return ret;
}

void HandleChildNode(ChildNode node)
{
    NodeHandler handler;
    if (TaskHandleSwitcher.TryGetValue(Type.GetRuntimeType(node), out handler))
    {
        handler(node);
    }
    else
    {
        //Unexpected type...
    }
}

Ceci est similaire à ce que Ted Elliot a suggéré, mais l'utilisation de descripteurs de type à l'exécution au lieu d'objets de type complets évite la surcharge liée au chargement de l'objet de type par réflexion.

Voici quelques timings rapides sur ma machine:

 Tester 3 itérations avec 5 000 000 d’éléments de données (mode = Random) et 5 types 
 Méthode Temps% optimal 
 If/Ailleurs 179.67. 100.00 
 TypeHandleDictionary 321.33 178.85 
 TypeDictionnaire 377.67 210.20 
 Basculer 492.67 274.21 

 Test de 3 itérations avec 5 000 000 éléments de données (mode = Aléatoire) et 10 types 
 Méthode Temps% de la valeur optimale 
 Si/Autre 271.33 100.00 
 TypeHandleDictionary 312.00 114.99 
 TypeDictionary 374.33 137.96 
 Switch 490.33 180.71 

 Test de 3 itérations avec 5 000 000 de données (mode = Random) et 15 types 
 Méthode Temps% de l’optimum 
 TypeHandleDictionary 312,00 100,00 
 If/Else 369.00 118.27 
 TypeDictionary 371.67 119.12 
 Switch 491.67 157.59 

 Tester 3 itérations avec 5 000 000 de données (mode = aléatoire) et 20 types 
 Méthode Temps% de la valeur optimale 
 T ypeHandleDictionary 335.33 100.00 
 TypeDictionary 373.00 111.23 
 If/Else 462.67 137.97 
 Commutateur 490.33 146.22 

 Tester 3 itérations avec 5 000 000 de données (mode = Aléatoire) et 25 types 
 Méthode Temps% of optimal 
 TypeHandleDictionary 319.33 100.00 
 TypeDictionary 371.00 116.18 
 Switch 483.00 151.25 
 If/Else 562.00 175.99 

 Tester 3 itérations avec 5 000 000 de données (mode = Aléatoire) et 50 types
 Méthode Temps% optimal 
 TypeHandleDictionary 319.67 100.00 
 TypeDictionary 376.67 117.83 
 Commutateur 453.33 141.81 
 If/Else 1,032.67 323.04 

Sur ma machine au moins, l’approche du dictionnaire des descripteurs de types bat tous les autres pour tout ce qui dépasse 15 types différents lorsque la distribution 
 Des types utilisés en entrée de la méthode est aléatoire.

Si d'autre part, l'entrée est entièrement composée du type qui est vérifié en premier dans la chaîne if/else et que cette méthode est beaucoup plus rapide: 

Tester 3 itérations avec 5 000 000 d'éléments de données (mode = UniformFirst) et 50 types Méthode Temps% optimal Si/Sinon 39,00 100,00 TypeHandleDictionary 317.33 813.68 TypeDictionary 396,00 1 015.38 403,00 1 033,33

Inversement, si l'entrée est toujours la dernière chose dans la chaîne if/else, cela a l'effet inverse:

Tester 3 itérations avec 5 000 000 de données (mode = UniformLast) et 50 types Méthode Temps% optimal TypeHandleDictionary 317.67 100.00. Switch 354.33 111.54 TypeDictionary 377.67 118.89 Si/Sinon 1 907,67 600,52

Si vous pouvez faire des suppositions à propos de votre entrée, vous obtiendrez peut-être les meilleures performances d'une approche hybride, où vous effectuerez si/else recherche les quelques types les plus courants, puis retombez dans une approche basée sur le dictionnaire si celles-ci échouent.

If you can make some assumptions about your input, you might get the best performance from a hybrid approach where you perform if/else checks for the few types that are most common, and then fall back to a dictionary-driven approach if those fail.

122
Andrew

Je viens de mettre en place une application de test rapide et de la profiler avec ANTS 4.
Spéc. .Sp: .Net 3.5 SP1 dans Windows XP 32 bits, code construit en mode de libération.

3 millions de tests: 

  • Commutateur: 1.842 secondes
  • Si: 0,344 seconde.

De plus, les résultats de l'instruction switch révèlent (sans surprise) que les noms longs prennent plus de temps.

1 million de tests 

  • Bob: 0,612 secondes. 
  • Jill: 0,835 seconde. 
  • Marko: 1.093 secondes.

Je ressemble à "Si Else" est plus rapide, au moins le scénario que j'ai créé. 

class Program
{
    static void Main( string[] args )
    {
        Bob bob = new Bob();
        Jill jill = new Jill();
        Marko marko = new Marko();

        for( int i = 0; i < 1000000; i++ )
        {
            Test( bob );
            Test( jill );
            Test( marko );
        }
    }

    public static void Test( ChildNode childNode )
    {   
        TestSwitch( childNode );
        TestIfElse( childNode );
    }

    private static void TestIfElse( ChildNode childNode )
    {
        if( childNode is Bob ){}
        else if( childNode is Jill ){}
        else if( childNode is Marko ){}
    }

    private static void TestSwitch( ChildNode childNode )
    {
        switch( childNode.Name )
        {
            case "Bob":
                break;
            case "Jill":
                break;
            case "Marko":
                break;
        }
    }
}

class ChildNode { public string Name { get; set; } }

class Bob : ChildNode { public Bob(){ this.Name = "Bob"; }}

class Jill : ChildNode{public Jill(){this.Name = "Jill";}}

class Marko : ChildNode{public Marko(){this.Name = "Marko";}}
18
Greg

Tout d'abord, vous comparez des pommes et des oranges. Vous devez d’abord comparer le type et la chaîne, puis comparer le type et la chaîne, puis comparer les gagnants.

Deuxièmement, c’est le genre de chose pour laquelle OO a été conçu. Dans les langues qui prennent en charge OO, le type d'activation (de tout type) est une odeur de code indiquant une mauvaise conception. La solution consiste à dériver d'une base commune avec une méthode abstraite ou virtuelle (ou une construction similaire, selon votre langage).

par exemple.

class Node
{
    public virtual void Action()
    {
        // Perform default action
    }
}

class Bob : Node
{
    public override void Action()
    {
        // Perform action for Bill
    }
}

class Jill : Node
{
    public override void Action()
    {
        // Perform action for Jill
    }
}

Ensuite, au lieu de faire l’instruction switch, vous appelez simplement childNode.Action ()

17
ilitirit

L'instruction Switch est plus rapide à exécuter que l'échelle if-else-if. Cela est dû à la capacité du compilateur à optimiser l'instruction switch. Dans le cas de l'échelle if-else-if, le code doit traiter chaque instruction if dans l'ordre déterminé par le programmeur. Cependant, comme chaque cas dans une instruction switch ne s'appuie pas sur des cas antérieurs, le compilateur est en mesure de réorganiser les tests de manière à fournir l'exécution la plus rapide.

12
Nescio

Si les cours sont faits, je suggérerais d'utiliser un modèle de conception Stratégie au lieu de switch ou else.

6
Gary Kephart

Essayez d'utiliser des énumérations pour chaque objet, vous pouvez activer les énumérations rapidement et facilement.

4
Rick Minerich

À moins que vous n'ayez déjà écrit ceci et constaté que vous avez un problème de performances, je ne m'inquiéterais pas de ce qui est plus rapide. Allez avec celui qui est plus lisible. Rappelez-vous, "l'optimisation prématurée est la racine de tout le mal." - Donald Knuth

3
Chris Upchurch

Une construction SWITCH était à l’origine destinée à des données entières; L'intention était d'utiliser l'argument directement comme un index dans une "table de répartition", une table de pointeurs. En tant que tel, il y aurait un seul test, puis lancer directement dans le code pertinent, plutôt qu'une série de tests.

La difficulté ici est que son utilisation a été généralisée aux types "string", qui ne peuvent évidemment pas être utilisés en tant qu'index, et que tous les avantages de la construction SWITCH sont perdus.

Si la vitesse est votre objectif prévu, le problème n'est PAS votre code, mais votre structure de données. Si l'espace "name" est aussi simple que vous le montrez, il est préférable de le coder en valeur entière (lors de la création de données, par exemple) et d'utiliser cet entier dans "plusieurs fois dans une partie lente de l'application".

3
user17983

Si les types que vous activez sont des types .NET primitifs, vous pouvez utiliser Type.GetTypeCode (Type), mais s'ils sont personnalisés, ils reviendront tous sous le nom TypeCode.Object. 

Un dictionnaire avec des délégués ou des classes de gestionnaires peut également fonctionner.

Dictionary<Type, HandlerDelegate> handlers = new Dictionary<Type, HandlerDelegate>();
handlers[typeof(Bob)] = this.HandleBob;
handlers[typeof(Jill)] = this.HandleJill;
handlers[typeof(Marko)] = this.HandleMarko;

handlers[childNode.GetType()](childNode);
/// ...

private void HandleBob(Node childNode) {
    // code to handle Bob
}
3
Ted Elliott

J'ai créé une petite console pour montrer ma solution, juste pour souligner la différence de vitesse. J'ai utilisé un algorithme de hachage de chaîne différent, car la version du certificat est lente pour l'exécution et les doublons sont peu probables. Dans l'affirmative, mon instruction switch échouerait (jamais jusqu'à maintenant). Ma méthode d'extension de hachage unique est incluse dans le code ci-dessous.

 Core 2 console app with output

Je vais prendre 29 ticks sur 695 ticks à tout moment, spécialement lorsque j'utilise du code critique.

Avec un ensemble de chaînes d'une base de données donnée, vous pouvez créer une petite application pour créer la constante dans un fichier donné que vous utiliserez dans votre code. Si des valeurs sont ajoutées, il vous suffit de réexécuter votre lot et les constantes sont générées et récupérées par la solution.

  public static class StringExtention
    {
        public static long ToUniqueHash(this string text)
        {
            long value = 0;
            var array = text.ToCharArray();
            unchecked
            {
                for (int i = 0; i < array.Length; i++)
                {
                    value = (value * 397) ^ array[i].GetHashCode();
                    value = (value * 397) ^ i;
                }
                return value;
            }
        }
    }

    public class AccountTypes
    {

        static void Main()
        {
            var sb = new StringBuilder();

            sb.AppendLine($"const long ACCOUNT_TYPE = {"AccountType".ToUniqueHash()};");
            sb.AppendLine($"const long NET_LIQUIDATION = {"NetLiquidation".ToUniqueHash()};");
            sb.AppendLine($"const long TOTAL_CASH_VALUE = {"TotalCashValue".ToUniqueHash()};");
            sb.AppendLine($"const long SETTLED_CASH = {"SettledCash".ToUniqueHash()};");
            sb.AppendLine($"const long ACCRUED_CASH = {"AccruedCash".ToUniqueHash()};");
            sb.AppendLine($"const long BUYING_POWER = {"BuyingPower".ToUniqueHash()};");
            sb.AppendLine($"const long EQUITY_WITH_LOAN_VALUE = {"EquityWithLoanValue".ToUniqueHash()};");
            sb.AppendLine($"const long PREVIOUS_EQUITY_WITH_LOAN_VALUE = {"PreviousEquityWithLoanValue".ToUniqueHash()};");
            sb.AppendLine($"const long GROSS_POSITION_VALUE ={ "GrossPositionValue".ToUniqueHash()};");
            sb.AppendLine($"const long REQT_EQUITY = {"ReqTEquity".ToUniqueHash()};");
            sb.AppendLine($"const long REQT_MARGIN = {"ReqTMargin".ToUniqueHash()};");
            sb.AppendLine($"const long SPECIAL_MEMORANDUM_ACCOUNT = {"SMA".ToUniqueHash()};");
            sb.AppendLine($"const long INIT_MARGIN_REQ = { "InitMarginReq".ToUniqueHash()};");
            sb.AppendLine($"const long MAINT_MARGIN_REQ = {"MaintMarginReq".ToUniqueHash()};");
            sb.AppendLine($"const long AVAILABLE_FUNDS = {"AvailableFunds".ToUniqueHash()};");
            sb.AppendLine($"const long EXCESS_LIQUIDITY = {"ExcessLiquidity".ToUniqueHash()};");
            sb.AppendLine($"const long CUSHION = {"Cushion".ToUniqueHash()};");
            sb.AppendLine($"const long FULL_INIT_MARGIN_REQ = {"FullInitMarginReq".ToUniqueHash()};");
            sb.AppendLine($"const long FULL_MAINTMARGIN_REQ ={ "FullMaintMarginReq".ToUniqueHash()};");
            sb.AppendLine($"const long FULL_AVAILABLE_FUNDS = {"FullAvailableFunds".ToUniqueHash()};");
            sb.AppendLine($"const long FULL_EXCESS_LIQUIDITY ={ "FullExcessLiquidity".ToUniqueHash()};");
            sb.AppendLine($"const long LOOK_AHEAD_INIT_MARGIN_REQ = {"LookAheadInitMarginReq".ToUniqueHash()};");
            sb.AppendLine($"const long LOOK_AHEAD_MAINT_MARGIN_REQ = {"LookAheadMaintMarginReq".ToUniqueHash()};");
            sb.AppendLine($"const long LOOK_AHEAD_AVAILABLE_FUNDS = {"LookAheadAvailableFunds".ToUniqueHash()};");
            sb.AppendLine($"const long LOOK_AHEAD_EXCESS_LIQUIDITY = {"LookAheadExcessLiquidity".ToUniqueHash()};");
            sb.AppendLine($"const long HIGHEST_SEVERITY = {"HighestSeverity".ToUniqueHash()};");
            sb.AppendLine($"const long DAY_TRADES_REMAINING = {"DayTradesRemaining".ToUniqueHash()};");
            sb.AppendLine($"const long LEVERAGE = {"Leverage".ToUniqueHash()};");
            Console.WriteLine(sb.ToString());

            Test();    
        }    

        public static void Test()
        {
            //generated constant values
            const long ACCOUNT_TYPE = -3012481629590703298;
            const long NET_LIQUIDATION = 5886477638280951639;
            const long TOTAL_CASH_VALUE = 2715174589598334721;
            const long SETTLED_CASH = 9013818865418133625;
            const long ACCRUED_CASH = -1095823472425902515;
            const long BUYING_POWER = -4447052054809609098;
            const long EQUITY_WITH_LOAN_VALUE = -4088154623329785565;
            const long PREVIOUS_EQUITY_WITH_LOAN_VALUE = 6224054330592996694;
            const long GROSS_POSITION_VALUE = -7316842993788269735;
            const long REQT_EQUITY = -7457439202928979430;
            const long REQT_MARGIN = -7525806483981945115;
            const long SPECIAL_MEMORANDUM_ACCOUNT = -1696406879233404584;
            const long INIT_MARGIN_REQ = 4495254338330797326;
            const long MAINT_MARGIN_REQ = 3923858659879350034;
            const long AVAILABLE_FUNDS = 2736927433442081110;
            const long EXCESS_LIQUIDITY = 5975045739561521360;
            const long CUSHION = 5079153439662500166;
            const long FULL_INIT_MARGIN_REQ = -6446443340724968443;
            const long FULL_MAINTMARGIN_REQ = -8084126626285123011;
            const long FULL_AVAILABLE_FUNDS = 1594040062751632873;
            const long FULL_EXCESS_LIQUIDITY = -2360941491690082189;
            const long LOOK_AHEAD_INIT_MARGIN_REQ = 5230305572167766821;
            const long LOOK_AHEAD_MAINT_MARGIN_REQ = 4895875570930256738;
            const long LOOK_AHEAD_AVAILABLE_FUNDS = -7687608210548571554;
            const long LOOK_AHEAD_EXCESS_LIQUIDITY = -4299898188451362207;
            const long HIGHEST_SEVERITY = 5831097798646393988;
            const long DAY_TRADES_REMAINING = 3899479916235857560;
            const long LEVERAGE = 1018053116254258495;

            bool found = false;
            var sValues = new string[] {
              "AccountType"
              ,"NetLiquidation"
              ,"TotalCashValue"
              ,"SettledCash"
              ,"AccruedCash"
              ,"BuyingPower"
              ,"EquityWithLoanValue"
              ,"PreviousEquityWithLoanValue"
              ,"GrossPositionValue"
              ,"ReqTEquity"
              ,"ReqTMargin"
              ,"SMA"
              ,"InitMarginReq"
              ,"MaintMarginReq"
              ,"AvailableFunds"
              ,"ExcessLiquidity"
              ,"Cushion"
              ,"FullInitMarginReq"
              ,"FullMaintMarginReq"
              ,"FullAvailableFunds"
              ,"FullExcessLiquidity"
              ,"LookAheadInitMarginReq"
              ,"LookAheadMaintMarginReq"
              ,"LookAheadAvailableFunds"
              ,"LookAheadExcessLiquidity"
              ,"HighestSeverity"
              ,"DayTradesRemaining"
              ,"Leverage"
            };

            long t1, t2;
            var sw = System.Diagnostics.Stopwatch.StartNew();
            foreach (var name in sValues)
            {
                switch (name)
                {
                    case "AccountType": found = true; break;
                    case "NetLiquidation": found = true; break;
                    case "TotalCashValue": found = true; break;
                    case "SettledCash": found = true; break;
                    case "AccruedCash": found = true; break;
                    case "BuyingPower": found = true; break;
                    case "EquityWithLoanValue": found = true; break;
                    case "PreviousEquityWithLoanValue": found = true; break;
                    case "GrossPositionValue": found = true; break;
                    case "ReqTEquity": found = true; break;
                    case "ReqTMargin": found = true; break;
                    case "SMA": found = true; break;
                    case "InitMarginReq": found = true; break;
                    case "MaintMarginReq": found = true; break;
                    case "AvailableFunds": found = true; break;
                    case "ExcessLiquidity": found = true; break;
                    case "Cushion": found = true; break;
                    case "FullInitMarginReq": found = true; break;
                    case "FullMaintMarginReq": found = true; break;
                    case "FullAvailableFunds": found = true; break;
                    case "FullExcessLiquidity": found = true; break;
                    case "LookAheadInitMarginReq": found = true; break;
                    case "LookAheadMaintMarginReq": found = true; break;
                    case "LookAheadAvailableFunds": found = true; break;
                    case "LookAheadExcessLiquidity": found = true; break;
                    case "HighestSeverity": found = true; break;
                    case "DayTradesRemaining": found = true; break;
                    case "Leverage": found = true; break;
                    default: found = false; break;
                }

                if (!found)
                    throw new NotImplementedException();
            }
            t1 = sw.ElapsedTicks;
            sw.Restart();
            foreach (var name in sValues)
            {
                switch (name.ToUniqueHash())
                {
                    case ACCOUNT_TYPE:
                        found = true;
                        break;
                    case NET_LIQUIDATION:
                        found = true;
                        break;
                    case TOTAL_CASH_VALUE:
                        found = true;
                        break;
                    case SETTLED_CASH:
                        found = true;
                        break;
                    case ACCRUED_CASH:
                        found = true;
                        break;
                    case BUYING_POWER:
                        found = true;
                        break;
                    case EQUITY_WITH_LOAN_VALUE:
                        found = true;
                        break;
                    case PREVIOUS_EQUITY_WITH_LOAN_VALUE:
                        found = true;
                        break;
                    case GROSS_POSITION_VALUE:
                        found = true;
                        break;
                    case REQT_EQUITY:
                        found = true;
                        break;
                    case REQT_MARGIN:
                        found = true;
                        break;
                    case SPECIAL_MEMORANDUM_ACCOUNT:
                        found = true;
                        break;
                    case INIT_MARGIN_REQ:
                        found = true;
                        break;
                    case MAINT_MARGIN_REQ:
                        found = true;
                        break;
                    case AVAILABLE_FUNDS:
                        found = true;
                        break;
                    case EXCESS_LIQUIDITY:
                        found = true;
                        break;
                    case CUSHION:
                        found = true;
                        break;
                    case FULL_INIT_MARGIN_REQ:
                        found = true;
                        break;
                    case FULL_MAINTMARGIN_REQ:
                        found = true;
                        break;
                    case FULL_AVAILABLE_FUNDS:
                        found = true;
                        break;
                    case FULL_EXCESS_LIQUIDITY:
                        found = true;
                        break;
                    case LOOK_AHEAD_INIT_MARGIN_REQ:
                        found = true;
                        break;
                    case LOOK_AHEAD_MAINT_MARGIN_REQ:
                        found = true;
                        break;
                    case LOOK_AHEAD_AVAILABLE_FUNDS:
                        found = true;
                        break;
                    case LOOK_AHEAD_EXCESS_LIQUIDITY:
                        found = true;
                        break;
                    case HIGHEST_SEVERITY:
                        found = true;
                        break;
                    case DAY_TRADES_REMAINING:
                        found = true;
                        break;
                    case LEVERAGE:
                        found = true;
                        break;
                    default:
                        found = false;
                        break;
                }

                if (!found)
                    throw new NotImplementedException();
            }
            t2 = sw.ElapsedTicks;
            sw.Stop();
            Console.WriteLine($"String switch:{t1:N0} long switch:{t2:N0}");
            var faster = (t1 > t2) ? "Slower" : "faster";
            Console.WriteLine($"String switch: is {faster} than long switch: by {Math.Abs(t1-t2)} Ticks");
            Console.ReadLine();

        }

Je me souviens avoir lu dans plusieurs ouvrages de référence que le branchement if/else est plus rapide que l’instruction switch. Cependant, un peu de recherche sur Blackwasp montre que l’instruction switch est en réalité plus rapide: http://www.blackwasp.co.uk/SpeedTestIfElseSwitch.aspx

En réalité, si vous comparez les déclarations types de 3 à 10 (ou plus), je doute fort qu'il y ait un réel gain de performance en utilisant l'une ou l'autre.

Comme Chris l'a déjà dit, privilégiez la lisibilité: Qu'est-ce qui est plus rapide, allumer chaîne ou elseif sur type?

2
Metro Smurf

Le commutateur () compilera en code équivalent à un ensemble de ifs. Les comparaisons de chaînes seront beaucoup plus lentes que les comparaisons de types.

2
moonshadow

Je pense que le principal problème de performances ici est que, dans le bloc switch, vous comparez les chaînes, et que dans le bloc if-else, vous vérifiez les types ... Ces deux types ne sont pas identiques, et par conséquent, je dirais que vous 'compare les pommes de terre aux bananes ".

Je commencerais par comparer ceci:

switch(childNode.Name)
{
    case "Bob":
        break;
    case "Jill":
        break;
    case "Marko":
      break;
}

if(childNode.Name == "Bob")
{}
else if(childNode.Name == "Jill")
{}
else if(childNode.Name == "Marko")
{}
2
SaguiItay

Je ne suis pas sûr de la rapidité avec laquelle le bon design pourrait être utilisé pour le polymorphisme. 

interface INode
{
    void Action;
}

class Bob : INode
{
    public void Action
    {

    }
}

class Jill : INode
{
    public void Action
    {

    }
}

class Marko : INode
{
    public void Action
    {

    }
}

//Your function:
void Do(INode childNode)
{
    childNode.Action();
}

Voir ce que fait votre déclaration switch aidera mieux. Si votre fonction ne concerne pas vraiment une action sur le type, vous pouvez peut-être définir une énumération sur chaque type.

enum NodeType { Bob, Jill, Marko, Default }

interface INode
{
    NodeType Node { get; };
}

class Bob : INode
{
    public NodeType Node { get { return NodeType.Bob; } }
}

class Jill : INode
{
    public NodeType Node { get { return NodeType.Jill; } }
}

class Marko : INode
{
    public NodeType Node { get { return NodeType.Marko; } }
}

//Your function:
void Do(INode childNode)
{
    switch(childNode.Node)
    {
        case Bob:
          break;
        case Jill:
          break;
        case Marko:
          break;
        Default:
          throw new ArgumentException();
    }
}

Je suppose que cela doit être plus rapide que les deux approches en question. Vous voudrez peut-être essayer abstract class route si les nanosecondes vous importent .

2
nawfal

Je viens de lire la liste des réponses ici, et je voulais partager ce test de référence qui compare la construction switch avec les opérateurs if-else et ternaire ?.

Ce que j’aime dans cet article n’est pas seulement une comparaison des constructions à gauche (par exemple, if-else) mais des constructions à double et à trois niveaux (par exemple, if-else-if-else).

Selon les résultats, la construction if-else était la plus rapide dans 8/9 cas de test; la construction switch à égalité pour le plus rapide dans 5/9 cas de test. 

Donc, si vous recherchez la vitesse, if-else semble être le moyen le plus rapide.

0
Thrawn Wannabe

Rappelez-vous que le profileur est votre ami. Toute conjecture est une perte de temps la plupart du temps ....... BTW, j'ai eu une bonne expérience avec JetBrains ' dotTrace profiler. 

0
Eddie Velasquez

Activer la chaîne est en principe compilée dans une échelle if-else-if. Essayez de décompiler un simple. Dans tous les cas, tester l'équation des chaînes devrait être moins coûteux, car elles sont internées et tout ce qui serait nécessaire serait une vérification des références. Faire ce qui a du sens en termes de maintenabilité; si vous comprenez des chaînes, faites le changement de chaîne. Si vous sélectionnez en fonction du type, une échelle de type est la plus appropriée.

0
nimish

La comparaison de chaînes dépendra toujours entièrement de l'environnement d'exécution (à moins que les chaînes ne soient allouées de manière statique, bien que la nécessité de les comparer entre elles soit discutable). La comparaison de types peut toutefois être effectuée via une liaison dynamique ou statique. En tout état de cause, il est plus efficace pour l'environnement d'exécution que de comparer des caractères individuels dans une chaîne.

0
Magsol

Le commutateur sur String compilerait sûrement jusqu'à une comparaison de chaîne (une par cas) qui est plus lente qu'une comparaison de type (et beaucoup plus lente que la comparaison d'entier typique utilisée pour un commutateur/cas)

0
JeeBee

Je fais en quelque sorte un peu différent, Les chaînes que vous activez vont être des constantes, de sorte que vous pouvez prédire les valeurs au moment de la compilation. 

dans votre cas, j'utiliserais les valeurs de hachage, ceci est un commutateur int, vous avez 2 options, utilisez les constantes de compilation ou calculez au moment de l'exécution.

//somewhere in your code
static long _bob = "Bob".GetUniqueHashCode();
static long _jill = "Jill".GetUniqueHashCode();
static long _marko = "Marko".GeUniquetHashCode();

void MyMethod()
{
   ...
   if(childNode.Tag==0)
      childNode.Tag= childNode.Name.GetUniquetHashCode()

   switch(childNode.Tag)
   {
       case _bob :
        break;
       case _jill :
         break;
       case _marko :
        break;
   }
}

La méthode d'extension pour GetUniquetHashCode peut ressembler à ceci:

public static class StringExtentions
    {
        /// <summary>
        /// Return unique Int64 value for input string
        /// </summary>
        /// <param name="strText"></param>
        /// <returns></returns>
        public static Int64 GetUniquetHashCode(this string strText)
        {
            Int64 hashCode = 0;
            if (!string.IsNullOrEmpty(strText))
            {
                //Unicode Encode Covering all character-set
                byte[] byteContents = Encoding.Unicode.GetBytes(strText);
                System.Security.Cryptography.SHA256 hash =  new System.Security.Cryptography.SHA256CryptoServiceProvider();
                byte[] hashText = hash.ComputeHash(byteContents);
                //32Byte hashText separate
                //hashCodeStart = 0~7  8Byte
                //hashCodeMedium = 8~23  8Byte
                //hashCodeEnd = 24~31  8Byte
                //and Fold
                Int64 hashCodeStart = BitConverter.ToInt64(hashText, 0);
                Int64 hashCodeMedium = BitConverter.ToInt64(hashText, 8);
                Int64 hashCodeEnd = BitConverter.ToInt64(hashText, 24);
                hashCode = hashCodeStart ^ hashCodeMedium ^ hashCodeEnd;
            }
            return (hashCode);
        }


    }

La source de ce code a été publiée ici .__ Veuillez noter que l’utilisation de la cryptographie est lente, vous devez généralement échauffer la chaîne prise en charge au démarrage de l’application. ne sont pas pertinents par exemple. veuillez noter que je définis la valeur de balise de l'objet node, je pourrais utiliser n'importe quelle propriété ou en ajouter une, assurez-vous simplement que celles-ci sont synchronisées avec le texte réel. 

Je travaille sur des systèmes à faible latence et tous mes codes se présentent sous la forme d'une chaîne de commandes: valeur, commande: valeur .... 

maintenant, les commandes sont toutes connues sous le nom de valeurs entières de 64 bits, ce qui permet de gagner du temps CPU. 

L’un des problèmes que vous avez avec le commutateur est l’utilisation de chaînes de caractères, comme "Bob", cela entraînera beaucoup plus de cycles et de lignes dans le code compilé. L'IL généré est tenu de déclarer une chaîne, de la définir sur "Bob" puis de l'utiliser dans la comparaison. En gardant cela à l'esprit, vos instructions IF seront plus rapides.

PS. L'exemple d'Aeon ne fonctionnera pas car vous ne pouvez pas activer les types. (Non, je ne sais pas pourquoi exactement, mais nous l'avons essayé et cela ne fonctionne pas. Cela a à voir avec le type variable)

Si vous voulez tester cela, créez simplement une application séparée et deux méthodes simples qui font ce qui est écrit ci-dessus et utilisez quelque chose comme Ildasm.exe pour voir le IL. Vous remarquerez beaucoup moins de lignes dans l'IL de la méthode IF.

Ildasm est livré avec VisualStudio ... 

Page ILDASM - http://msdn.Microsoft.com/en-us/library/f7dy01k1(VS.80).aspx

Tutoriel ILDASM - http://msdn.Microsoft.com/en-us/library/aa309387(VS.71).aspx

0
Joe

Trois pensées:

1) Si vous voulez faire quelque chose de différent en fonction des types d'objets, il peut être judicieux de déplacer ce comportement dans ces classes. Ensuite, au lieu de switch ou if-else, appelez simplement childNode.DoSomething (). 

2) La comparaison des types sera beaucoup plus rapide que celle des chaînes.

3) Dans la conception if-else, vous pourrez peut-être tirer parti de la réorganisation des tests. Si les objets "Jill" constituent 90% des objets qui y passent, testez-les d'abord.

0
Mark Bessey