Was ist schneller, String einschalten oder sonst Typ?

78

Nehmen wir an, ich habe die Möglichkeit, einen Codepfad anhand eines Zeichenfolgenvergleichs zu identifizieren oder den Typ zu bestimmen:

Welches ist schneller und warum?

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

Update: Der Hauptgrund, warum ich dies frage, ist, dass die switch-Anweisung genau angibt, was als Fall gilt. Beispielsweise können Sie keine Variablen verwenden, sondern nur Konstanten, die in die Hauptassembly verschoben werden. Ich nahm an, dass es diese Einschränkung aufgrund einiger funky Sachen gab, die es tat. Wenn es nur in elseifs übersetzt wird (wie ein Poster kommentierte), warum sind dann in case-Anweisungen keine Variablen zulässig?

Vorsichtsmaßnahme: Ich optimiere nach. Diese Methode wird aufgerufen , viele Male in einem langsamen Teil der App.

Streit
quelle
Mich würde interessieren, warum swtich-Anweisungen auch keine Variablen zulassen.
Metro Schlumpf
fyi - Sie tun es in VB, aber nicht in C #.
Nescio
2
Zu Ihrer Information: C # 7 erlaubt jetzt Umschalt- / Groß- / Kleinschreibungstypen, sodass Sie es nicht mehr so ​​schreiben müssen, als ob Anweisungen. Das ist momentan wahrscheinlich die beste Option.
LanderV

Antworten:

138

Gregs Profilergebnisse sind für das genaue Szenario, das er abdeckte, großartig, aber interessanterweise ändern sich die relativen Kosten der verschiedenen Methoden dramatisch, wenn eine Reihe verschiedener Faktoren berücksichtigt werden, einschließlich der Anzahl der verglichenen Typen sowie der relativen Häufigkeit und aller Muster in den zugrunde liegenden Daten .

Die einfache Antwort lautet: Niemand kann Ihnen sagen, wie hoch der Leistungsunterschied in Ihrem speziellen Szenario sein wird. Sie müssen die Leistung in Ihrem eigenen System auf unterschiedliche Weise selbst messen, um eine genaue Antwort zu erhalten.

Die If / Else-Kette ist ein effektiver Ansatz für eine kleine Anzahl von Typvergleichen oder wenn Sie zuverlässig vorhersagen können, welche wenigen Typen die Mehrheit der angezeigten Typen ausmachen werden. Das potenzielle Problem bei diesem Ansatz besteht darin, dass mit zunehmender Anzahl von Typen auch die Anzahl der Vergleiche zunimmt, die ausgeführt werden müssen.

wenn ich folgendes ausführe:

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

Jede der vorherigen if-Bedingungen muss ausgewertet werden, bevor der richtige Block eingegeben wird. Andererseits

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

führt einen einfachen Sprung zum richtigen Codebit durch.

In Ihrem Beispiel wird es komplizierter, wenn Ihre andere Methode einen Schalter für Zeichenfolgen anstelle von Ganzzahlen verwendet, was etwas komplizierter wird. Auf einer niedrigen Ebene können Zeichenfolgen nicht auf die gleiche Weise wie ganzzahlige Werte eingeschaltet werden, sodass der C # -Compiler etwas Magie ausübt, damit dies für Sie funktioniert.

Wenn die switch-Anweisung "klein genug" ist (wobei der Compiler automatisch das tut, was er für am besten hält), generiert das Einschalten von Zeichenfolgen Code, der mit einer if / else-Kette identisch ist.

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

ist das gleiche wie:

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

Sobald die Liste der Elemente im Wörterbuch "groß genug" wird, erstellt der Compiler automatisch ein internes Wörterbuch, das die Zeichenfolgen im Switch einem ganzzahligen Index und anschließend einem auf diesem Index basierenden Switch zuordnet.

Es sieht ungefähr so ​​aus (Stellen Sie sich mehr Einträge vor, als ich tippen werde)

Ein statisches Feld wird an einem "versteckten" Ort definiert, der der Klasse zugeordnet ist, die die switch-Anweisung vom Typ enthält Dictionary<string, int>und einen verstümmelten Namen erhält

//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();
}

In einigen Schnelltests, die ich gerade ausgeführt habe, ist die If / Else-Methode ungefähr dreimal so schnell wie der Wechsel für 3 verschiedene Typen (wobei die Typen zufällig verteilt sind). Bei 25 Typen ist der Switch um einen kleinen Abstand (16%) schneller. Bei 50 Typen ist der Switch mehr als doppelt so schnell.

Wenn Sie eine große Anzahl von Typen einschalten möchten, würde ich eine dritte Methode vorschlagen:

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...
    }
}

Dies ähnelt dem, was Ted Elliot vorgeschlagen hat, aber die Verwendung von Laufzeit-Typ-Handles anstelle von Objekten vom vollständigen Typ vermeidet den Aufwand für das Laden des Typ-Objekts durch Reflexion.

Hier sind einige kurze Zeitangaben auf meiner Maschine:

Testen von 3 Iterationen mit 5.000.000 Datenelementen (Modus = Zufällig) und 5 Typen
Methodenzeit% des Optimums
If / Else 179,67 100,00
TypeHandleDictionary 321.33 178.85
TypeDictionary 377.67 210.20
Schalter 492.67 274.21

Testen von 3 Iterationen mit 5.000.000 Datenelementen (Modus = Zufällig) und 10 Typen
Methodenzeit% des Optimums
If / Else 271.33 100.00
TypeHandleDictionary 312.00 114.99
TypeDictionary 374.33 137.96
Schalter 490.33 180.71

Testen von 3 Iterationen mit 5.000.000 Datenelementen (Modus = Zufällig) und 15 Typen
Methodenzeit% des Optimums
TypeHandleDictionary 312.00 100.00
If / Else 369.00 118.27
TypeDictionary 371.67 119.12
Schalter 491.67 157.59

Testen von 3 Iterationen mit 5.000.000 Datenelementen (Modus = Zufällig) und 20 Typen
Methodenzeit% des Optimums
TypeHandleDictionary 335.33 100.00
TypeDictionary 373.00 111.23
If / Else 462.67 137.97
Schalter 490.33 146.22

Testen von 3 Iterationen mit 5.000.000 Datenelementen (Modus = Zufällig) und 25 Typen
Methodenzeit% des Optimums
TypeHandleDictionary 319.33 100.00
TypeDictionary 371.00 116.18
Schalter 483.00 151.25
If / Else 562.00 175.99

Testen von 3 Iterationen mit 5.000.000 Datenelementen (Modus = Zufällig) und 50 Typen
Methodenzeit% des Optimums
TypeHandleDictionary 319.67 100.00
TypeDictionary 376.67 117.83
Schalter 453.33 141.81
If / Else 1.032,67 323,04

Zumindest auf meinem Computer übertrifft der Typ-Handle-Wörterbuch-Ansatz alle anderen für mehr als 15 verschiedene Typen, wenn die Verteilung der als Eingabe für die Methode verwendeten Typen zufällig ist.

Wenn andererseits die Eingabe vollständig aus dem Typ besteht, der zuerst in der if / else-Kette überprüft wird, ist diese Methode viel schneller:

Testen von 3 Iterationen mit 5.000.000 Datenelementen (Modus = UniformFirst) und 50 Typen
Methodenzeit% des Optimums
If / Else 39.00 100.00
TypeHandleDictionary 317.33 813.68
TypeDictionary 396.00 1.015,38
Schalter 403.00 1.033.33

Wenn umgekehrt die Eingabe immer das Letzte in der if / else-Kette ist, hat dies den gegenteiligen Effekt:

Testen von 3 Iterationen mit 5.000.000 Datenelementen (Modus = UniformLast) und 50 Typen
Methodenzeit% des Optimums
TypeHandleDictionary 317.67 100.00
Schalter 354.33 111.54
TypeDictionary 377.67 118.89
If / Else 1.907,67 600,52

Wenn Sie einige Annahmen über Ihre Eingabe treffen können, erzielen Sie möglicherweise die beste Leistung mit einem hybriden Ansatz, bei dem Sie die wenigen am häufigsten verwendeten Typen prüfen und dann auf einen wörterbuchgesteuerten Ansatz zurückgreifen, wenn diese fehlschlagen.

Mani Gandham
quelle
Entschuldigung, ich habe diese Antwort früher verpasst. Verdient es auf jeden Fall, top zu sein.
Quibblesome
8
Das ist wahrscheinlich die beste Antwort auf etwas, das ich auf SO gesehen habe. Meine Güte ... Daumen hoch!
Ron L
19

Zunächst vergleichen Sie Äpfel und Orangen. Sie müssen zuerst den Einschalttyp mit dem Einschaltstring und dann den Typ mit dem String vergleichen und dann die Gewinner vergleichen.

Zweitens ist dies das, wofür OO entwickelt wurde. In Sprachen, die OO unterstützen, ist das Einschalten des Typs (jeglicher Art) ein Codegeruch, der auf ein schlechtes Design hinweist. Die Lösung besteht darin, von einer gemeinsamen Basis mit einer abstrakten oder virtuellen Methode (oder einem ähnlichen Konstrukt, abhängig von Ihrer Sprache) abzuleiten.

z.B.

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
    }
}

Anstatt die switch-Anweisung auszuführen, rufen Sie einfach childNode.Action () auf.

ilitirit
quelle
2
(Neben Lesbarkeit und Wartbarkeit) Eine interessante Frage wäre, wie gut dies im Vergleich zu den beiden anderen Ansätzen funktioniert. Hinweis: Sie hätten auch die Leistung des Teils berücksichtigt, für den die Implementierung Nodeausgewählt und instanziiert wurde (z. B. die Factory).
Matthijs Wessels
18

Ich habe gerade eine Schnelltestanwendung implementiert und sie mit ANTS 4 profiliert.
Spezifikation: .Net 3.5 sp1 in 32-Bit-Windows XP, Code im Release-Modus.

3 Millionen Tests:

  • Schalter: 1,842 Sekunden
  • Wenn: 0,344 Sekunden.

Darüber hinaus zeigen die Ergebnisse der switch-Anweisung (nicht überraschend), dass längere Namen länger dauern.

1 Million Tests

  • Bob: 0,612 Sekunden.
  • Jill: 0,835 Sekunden.
  • Marko: 1,093 Sekunden.

Ich sehe aus wie das "Wenn sonst" schneller ist, zumindest das Szenario, das ich erstellt habe.

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";}}
Greg
quelle
2
Dies ist so sinnvoll, da der Wechsel mit Zeichenfolgen erfolgt und Sie den Aufwand für die Zeichenfolgenkonvertierung berücksichtigen müssen. Was wäre, wenn jede Klasse eine Aufzählung hätte?
Rick Minerich
4
Ist "Bob" schneller, weil es kürzer ist oder weil es das erste ist?
Frank Szczerba
12

Die Switch-Anweisung ist schneller auszuführen als die if-else-if-Leiter. Dies liegt an der Fähigkeit des Compilers, die switch-Anweisung zu optimieren. Im Fall der if-else-if-Leiter muss der Code jede if-Anweisung in der vom Programmierer festgelegten Reihenfolge verarbeiten. Da jedoch jeder Fall innerhalb einer switch-Anweisung nicht auf früheren Fällen beruht, kann der Compiler die Tests so neu anordnen, dass die schnellste Ausführung möglich ist.

Nescio
quelle
Die Typvergleiche sind jedoch auch viel einfacher zu pflegen. - Versuchen Sie nicht vorzeitig zu optimieren.
Nescio
1
Dies ist hilfreich, um zu sagen, dass das Geschriebene nicht genau das ist, was ausgeführt wird, aber irreführend, wenn man darauf hinweist, dass IFs nicht wegoptimiert werden können. Ich bin kein Optimierer-Experte, aber ich betrachte eine Klasse in Reflector, in der ein If / ElseIf mit einer schreibgeschützten Variablen in IL genauso implementiert ist wie ein SWITCH.
Kevin Crumley
Ein if-else-if-Konstrukt kann vom Compiler tatsächlich in eine switch-Anweisung konvertiert werden. Warum sollte es nicht möglich sein?
Tara
6

Wenn Sie die Klassen erstellt haben, würde ich vorschlagen, ein Strategie-Entwurfsmuster anstelle von switch oder elseif zu verwenden.

Gary Kephart
quelle
Das ist ein ausgezeichneter Vorschlag! Lassen Sie das Objekt selbst entscheiden, was es tun muss.
Bob King
Das wäre viel eleganter und würde all diese Unruhe ersparen. Dies würde jedoch viel Refactoring in diesem Bereich erfordern und nur als letztes Mittel eingesetzt werden. Aber ich stimme Ihnen im Allgemeinen zu. : D
Quibblesome
4

Versuchen Sie, für jedes Objekt Aufzählungen zu verwenden. Sie können Aufzählungen schnell und einfach aktivieren.

Rick Minerich
quelle
3

Wenn Sie dies nicht bereits geschrieben haben und feststellen, dass Sie ein Leistungsproblem haben, würde ich mir keine Sorgen machen, welches schneller ist. Gehen Sie mit dem, der besser lesbar ist. Denken Sie daran: "Vorzeitige Optimierung ist die Wurzel allen Übels." - Donald Knuth

Chris Upchurch
quelle
1
Außer wir optimieren nicht vorzeitig. Wir optimieren nach. Entschuldigung, aber -1.
Quibblesome
1
Die Antworten hier sind nicht nur zum Nutzen des Fragestellers, sondern für jeden, der später vorbeikommt. Die Leute müssen erkennen, dass es nicht der beste Weg ist, diese Entscheidung aus Leistungsgründen im Voraus zu treffen.
Chris Upchurch
Oh toll, jetzt ist die Antwort, die besagt, dass die Frage irrelevant ist, vor allem die anderen Antworten, die tatsächlich versuchen, die Frage zu beantworten. WEG ZU KERLEN! : P
Quibblesome
@Quarrelsome: Die Warnung vor vorzeitiger Optimierung ist für jede Optimierungsfrage relevant, es sei denn, Sie geben an, dass Sie Ihren Code bereits profiliert und für zu langsam befunden haben. Wenn Sie das in Ihre Frage einfügen, wird meine Antwort wahrscheinlich nicht mehr gewählt.
Chris Upchurch
1
In Anbetracht der vorherrschenden vorzeitigen Optimierung ja.
Chris Upchurch
3

Ein SWITCH-Konstrukt war ursprünglich für ganzzahlige Daten vorgesehen. Es war beabsichtigt, das Argument direkt als Index für eine "Versandtabelle", eine Zeigertabelle, zu verwenden. Als solches würde es einen einzelnen Test geben und dann direkt zum relevanten Code starten, anstatt eine Reihe von Tests.

Die Schwierigkeit hierbei ist, dass seine Verwendung auf "String" -Typen verallgemeinert wurde, die offensichtlich nicht als Index verwendet werden können, und dass alle Vorteile des SWITCH-Konstrukts verloren gehen.

Wenn Geschwindigkeit Ihr beabsichtigtes Ziel ist, ist das Problem NICHT Ihr Code, sondern Ihre Datenstruktur. Wenn der Bereich "Name" so einfach ist, wie Sie ihn anzeigen, codieren Sie ihn besser in einen ganzzahligen Wert (z. B. wenn Daten erstellt werden) und verwenden Sie diese Ganzzahl im Abschnitt "Viele Male in einem langsamen Teil der App".

user17983
quelle
3

Wenn es sich bei den eingeschalteten Typen um primitive .NET-Typen handelt, können Sie Type.GetTypeCode (Type) verwenden. Wenn es sich jedoch um benutzerdefinierte Typen handelt, werden alle als TypeCode.Object zurückgegeben.

Ein Wörterbuch mit Delegaten oder Handlerklassen könnte ebenfalls funktionieren.

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
}
Ted Elliott
quelle
Süß. :) Irgendeine Idee, ob der Delegatenaufruf irgendwelche Auswirkungen auf die Leistung hat?
Quibblesome
2

Der Schalter () wird zu Code kompiliert, der einer Reihe anderer ifs entspricht. Die Zeichenfolgenvergleiche sind viel langsamer als die Typvergleiche.

Mondschatten
quelle
Führt die CLR keine funky Tricks innerhalb einer switch-Anweisung aus? Warum werden Sie sonst gezwungen, Konstanten nur als case-Anweisungen anstelle von Variablen zu verwenden, wenn sie nur in andere ifs übersetzt werden?
Quibblesome
Die CLR führt funky Tricks aus, wenn die switch-Anweisung Basistypen verwendet. Hier ist jedoch jeweils ein Zeichenfolgenvergleich erforderlich, sodass nur wenig Spielraum für Optimierungen besteht.
Mondschatten
1
C # kompiliert keine stringbasierten Switches zu String-Vergleichen. Da die Fallbezeichnungen Literale sein müssen, werden Tricks wie das Internieren der geschalteten Variablen, das Einschalten des Hashcodes und das Überprüfen der Objektidentität (die aufgrund des Internierens funktioniert) verwendet, um sicherzustellen, dass die Übereinstimmung korrekt ist.
Michael Burr
oOo das ist jetzt sehr interessant. Die CLR macht also eine Ausnahme für Zeichenfolgen, die sie für andere Typen nicht macht? Bedeutet dies also, dass es anderen ifs entspricht?
Quibblesome
2

Ich erinnere mich, dass ich in mehreren Nachschlagewerken gelesen habe, dass die Verzweigung von if / else schneller ist als die switch-Anweisung. Ein wenig Forschung über Blackwasp zeigt jedoch, dass die switch-Anweisung tatsächlich schneller ist: http://www.blackwasp.co.uk/SpeedTestIfElseSwitch.aspx

Wenn Sie in der Realität die typischen 3 bis 10 (oder so) Aussagen vergleichen, bezweifle ich ernsthaft, dass es mit der einen oder anderen einen echten Leistungsgewinn gibt.

Wie Chris bereits gesagt hat, achten Sie auf Lesbarkeit: Was geht schneller, schalten Sie den String ein oder sonst den Typ?

Metro Schlumpf
quelle
2

Ich denke, das Hauptproblem bei der Leistung ist, dass Sie im Switch-Block Zeichenfolgen vergleichen und im if-else-Block nach Typen suchen ... Diese beiden sind nicht gleich, und deshalb würde ich Ihnen sagen "Kartoffeln mit Bananen vergleichen".

Ich würde damit beginnen, dies zu vergleichen:

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")
{}
SaguiItay
quelle
1
Hallo, danke für die Antwort! Dies ist eigentlich das Szenario, das ich habe. Wir können entweder eine eindeutige ID (eine Zeichenfolge) oder den Objekttyp verwenden, um zwischen diesen Objekten zu unterscheiden.
Quibblesome
2

Ich bin mir nicht sicher, wie schnell das richtige Design für Polymorphismus sein könnte.

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

Zu sehen, was Ihre switch-Anweisung bewirkt, hilft besser. Wenn Ihre Funktion nicht wirklich etwas mit einer Aktion für den Typ zu tun hat, können Sie möglicherweise für jeden Typ eine Aufzählung definieren.

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

Ich gehe davon aus, dass dies schneller sein muss als beide fraglichen Ansätze. Vielleicht möchten Sie die abstrakte Klassenroute ausprobieren, wenn Nanosekunden für Sie von Bedeutung sind .

nawfal
quelle
2

Ich habe eine kleine Konsole erstellt, um meine Lösung zu zeigen, nur um den Geschwindigkeitsunterschied hervorzuheben. Ich habe einen anderen String-Hash-Algorithmus verwendet, da die Zertifikatversion zur Laufzeit für mich zu langsam ist und Duplikate unwahrscheinlich sind. In diesem Fall würde meine switch-Anweisung fehlschlagen (bisher noch nie geschehen). Meine einzigartige Hash-Erweiterungsmethode ist im folgenden Code enthalten.

Core 2 Konsolen-App mit Ausgabe

Ich werde jederzeit 29 Ticks über 695 Ticks nehmen, insbesondere wenn ich kritischen Code verwende.

Mit einer Reihe von Zeichenfolgen aus einer bestimmten Datenbank können Sie eine kleine Anwendung erstellen, um die Konstante in einer bestimmten Datei zu erstellen, die Sie in Ihrem Code verwenden können. Wenn Werte hinzugefügt werden, führen Sie einfach Ihren Stapel erneut aus und Konstanten werden von generiert und übernommen die Lösung.

  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();

        }
Walter Vehoeven
quelle
0

Der Zeichenfolgenvergleich hängt immer vollständig von der Laufzeitumgebung ab (es sei denn, die Zeichenfolgen sind statisch zugeordnet, obwohl die Notwendigkeit, diese miteinander zu vergleichen, umstritten ist). Der Typvergleich kann jedoch durch dynamische oder statische Bindung erfolgen. In beiden Fällen ist er für die Laufzeitumgebung effizienter als der Vergleich einzelner Zeichen in einer Zeichenfolge.

Magsol
quelle
0

Sicherlich würde der Einschalt-String zu einem String-Vergleich (einer pro Fall) kompiliert, der langsamer als ein Typvergleich ist (und weitaus langsamer als der typische Ganzzahl-Vergleich, der für Schalter / Fall verwendet wird).

JeeBee
quelle
0

Drei Gedanken:

1) Wenn Sie je nach Objekttyp etwas anderes tun, ist es möglicherweise sinnvoll, dieses Verhalten in diese Klassen zu verschieben. Dann würden Sie anstelle von switch oder if-else einfach childNode.DoSomething () aufrufen.

2) Der Vergleich von Typen ist viel schneller als der Vergleich von Zeichenfolgen.

3) Im if-else-Design können Sie möglicherweise die Möglichkeit nutzen, die Tests neu zu ordnen. Wenn "Jill" -Objekte 90% der dort durchlaufenden Objekte ausmachen, testen Sie sie zuerst.

Mark Bessey
quelle
0

Eines der Probleme, die Sie mit dem Schalter haben, ist die Verwendung von Zeichenfolgen wie "Bob". Dies führt zu viel mehr Zyklen und Zeilen im kompilierten Code. Die generierte IL muss eine Zeichenfolge deklarieren, auf "Bob" setzen und dann im Vergleich verwenden. In diesem Sinne werden Ihre IF-Anweisungen schneller ausgeführt.

PS. Das Beispiel von Aeon funktioniert nicht, da Sie Typen nicht einschalten können. (Nein, ich weiß nicht genau warum, aber wir haben es versucht und es funktioniert nicht. Es hat damit zu tun, dass der Typ variabel ist.)

Wenn Sie dies testen möchten, erstellen Sie einfach eine separate Anwendung und erstellen Sie zwei einfache Methoden, die das tun, was oben beschrieben wurde, und verwenden Sie etwas wie Ildasm.exe, um die IL anzuzeigen. Sie werden viel weniger Zeilen in der IL der IF-Anweisungsmethode bemerken.

Ildasm kommt mit VisualStudio ...

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

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


quelle
0

Denken Sie daran, der Profiler ist Ihr Freund. Vermutungen sind meistens Zeitverschwendung. Übrigens habe ich gute Erfahrungen mit dem dotTrace- Profiler von JetBrains gemacht .

Eddie Velasquez
quelle
0

Die Einschaltzeichenfolge wird im Grunde genommen zu einer If-else-If-Leiter kompiliert. Versuchen Sie, eine einfache zu dekompilieren. In jedem Fall sollte das Testen der Zeichenfolgengleichheit billiger sein, da sie interniert sind und lediglich eine Referenzprüfung erforderlich wäre. Tun Sie, was in Bezug auf die Wartbarkeit sinnvoll ist. Wenn Sie Zeichenfolgen enthalten, wechseln Sie die Zeichenfolge. Wenn Sie nach Typ auswählen, ist eine Typleiter am besten geeignet.

nimish
quelle
0

Ich mache es ein bisschen anders. Die Zeichenfolgen, die Sie einschalten, werden Konstanten sein, sodass Sie die Werte zur Kompilierungszeit vorhersagen können.

In Ihrem Fall würde ich die Hash-Werte verwenden, dies ist ein int-Schalter, Sie haben 2 Optionen, verwenden Kompilierungszeitkonstanten oder berechnen zur Laufzeit.

//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;
   }
}

Die Erweiterungsmethode für GetUniquetHashCode kann ungefähr so ​​aussehen:

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


    }

Die Quelle dieses Codes wurde veröffentlicht hier veröffentlicht Bitte beachten Sie, dass die Verwendung von Kryptografie langsam ist. Normalerweise wird die unterstützte Zeichenfolge beim Start der Anwendung aufgewärmt. Ich speichere sie in statischen Feldern, da sie sich nicht ändern und nicht instanzrelevant sind. Bitte beachten Sie, dass ich den Tag-Wert des Knotenobjekts festlege, jede Eigenschaft verwenden oder eine hinzufügen kann. Stellen Sie einfach sicher, dass diese mit dem tatsächlichen Text synchron sind.

Ich arbeite auf Systemen mit geringer Latenz und alle meine Codes werden als Befehlsfolge geliefert: Wert, Befehl: Wert ....

Jetzt sind alle Befehle als 64-Bit-Integer-Werte bekannt, sodass eine solche Umschaltung CPU-Zeit spart.

Walter Vehoeven
quelle
0

Ich habe gerade die Liste der Antworten hier durchgelesen und wollte diesen Benchmark-Test teilen , der das switchKonstrukt mit dem if-elseund dem ternären vergleicht? Operatoren vergleicht.

Was ich an diesem Beitrag mag , ist, dass er nicht nur Einzel-Links-Konstrukte (z. B. if-else) vergleicht, sondern auch Doppel- und Dreifach-Konstrukte (z if-else-if-else. B. ).

Den Ergebnissen zufolge war das if-elseKonstrukt in 8/9 Testfällen am schnellsten; dasswitch Konstrukt war in 5/9 Testfällen am schnellsten.

Wenn Sie also nach Geschwindigkeit suchen, if-elsescheint dies der schnellste Weg zu sein.


quelle
-1

Möglicherweise fehlt mir etwas, aber können Sie nicht anstelle des Strings eine switch-Anweisung für den Typ ausführen? Das ist,

switch(childNode.Type)
{
case Bob:
  break;
case Jill:
  break;
case Marko:
  break;
}
Äon
quelle
Nein, die Umschaltoperation funktioniert nicht bei Objekten wie Typen. Nur "Integrale Typen"
Quibblesome
Ein String ist kein integraler Typ!
Mondschatten
Warum erlaubt das Framework dann eine Zeichenfolge, aber keinen Typ mit dem Compilerfehler: "Ein Wert eines erwarteten integralen Typs". Ist es nur ein kleiner Trick, mit dem Sie Zeichenfolgen verwenden können, obwohl es sich nicht um integrale Typen handelt?
Quibblesome
Ohh. Ok, sorry :) Ich weiß nicht, c #, es wäre logisch gewesen, Dinge, die als if-Bedingung gültig sind, als Switch-Bedingung zuzulassen.
Aeon
@ Quibblesome es ist in der Tat ein Trick. Switch Case kann String verarbeiten, obwohl es sich nicht um integrale Typen handelt
Nawfal