Що швидше, увімкнути рядок чи ще якщо тип?


78

Скажімо, у мене є можливість ідентифікувати шлях коду на основі порівняння рядків, або, якщо вказати тип:

Що швидше і чому?

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

Оновлення: Основна причина, по якій я запитую це, полягає в тому, що оператор switch є перкулярним щодо того, що вважається випадком. Наприклад, це не дозволяє вам використовувати змінні, лише константи, які переміщуються до основної збірки. Я припустив, що у нього є це обмеження через деякі забавні речі, які він робив. Якщо це переклад лише на elseifs (як зауважив один з плакатів), то чому нам заборонені змінні у заявах case?

Застереження: я здійснюю пост-оптимізацію. Цей метод викликається багато разів у повільній частині програми.


Мені було б цікаво дізнатися, чому оператори swtich також не допускають змінних.
Metro Smurf

fyi - Вони працюють у VB, але не в C #.
Nescio



2
FYI: C # 7 тепер дозволяє перемикання / регістр між типами, тому вам більше не доведеться писати це як оператори. Це, мабуть, найкращий варіант зараз.
LanderV

Відповіді:


138

Результати профілю Грега чудово підходять для точного сценарію, який він охопив, але що цікаво, відносні витрати різних методів різко змінюються, враховуючи низку різних факторів, включаючи кількість типів, що порівнюються, і відносну частоту та будь-які закономірності в базових даних .

Проста відповідь полягає в тому, що ніхто не може сказати вам, якою буде різниця в продуктивності у вашому конкретному сценарії; вам потрібно буде виміряти продуктивність по-різному самостійно у вашій власній системі, щоб отримати точну відповідь.

Мережа If / Else є ефективним підходом для невеликої кількості порівнянь типів, або якщо ви можете достовірно передбачити, які декілька типів складатимуть більшість із тих, які ви бачите. Потенційна проблема підходу полягає в тому, що зі збільшенням кількості типів збільшується і кількість порівнянь, які необхідно виконати.

якщо я виконаю наступне:

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

кожен з попередніх, якщо умови повинні бути оцінені перед введенням правильного блоку. З іншої сторони

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

виконає один простий перехід до правильного біта коду.

Де це ускладнюється у вашому прикладі, це те, що ваш інший метод використовує перемикач на рядки, а не цілі числа, що стає дещо складнішим. На низькому рівні рядки не можна вмикати так само, як цілі значення, тому компілятор C # робить магію, щоб зробити це для вас.

Якщо оператор switch є "досить малим" (де компілятор робить те, що вважає найкращим автоматично), перемикання рядків генерує код, такий самий, як ланцюжок if / else.

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

те саме, що:

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

Як тільки список елементів у словнику стане "достатньо великим", компілятор автоматично створює внутрішній словник, який зі рядків у комутаторі перетворюється на цілочисельний індекс, а потім перемикач на основі цього індексу.

Це виглядає приблизно так (Тільки уявіть більше записів, ніж я збираюся турбуватись надрукувати)

Статичне поле визначається в "прихованому" розташуванні, яке пов'язане з класом, що містить оператор перемикача типу Dictionary<string, int>і надане зіпсоване ім'я

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

У деяких швидких тестах, які я щойно провів, метод If / Else приблизно в 3 рази швидший, ніж перемикач для 3 різних типів (де типи розподіляються випадковим чином). У 25 типів комутатор швидший з невеликим відривом (16%), у 50 типів комутатор більш ніж удвічі швидший.

Якщо ви збираєтеся вмикати велику кількість типів, я б запропонував 3-й метод:

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

Це схоже на те, що запропонував Тед Елліот, але використання дескрипторів типу виконання замість об’єктів повного типу дозволяє уникнути накладних витрат на завантаження об’єкта типу шляхом відображення.

Ось короткі терміни на моїй машині:

Тестування 3 ітерацій з 5 000 000 елементами даних (режим = Випадковий) та 5 типів
Метод Час% від оптимального
Якщо / Інакше 179,67 100,00
Словник TypeHandleD словник 321,33 178,85
Наберіть словник 377,67 210,20
Перемикач 492,67 274,21

Тестування 3 ітерацій з 5 000 000 елементів даних (режим = Випадковий) та 10 типів
Метод Час% від оптимального
Якщо / Інакше 271,33 100,00
Словник TypeHandleD словник 312,00 114,99
Наберіть словник 374,33 137,96
Перемикач 490,33 180,71

Тестування 3 ітерацій з 5 000 000 елементів даних (режим = Випадковий) та 15 типів
Метод Час% від оптимального
TypeHandleDictionary 312,00 100,00
Якщо / Інакше 369,00 118,27
Типсловник 371,67 119,12
Перемикач 491,67 157,59

Тестування 3 ітерацій з 5 000 000 елементами даних (режим = Випадковий) та 20 типами
Метод Час% від оптимального
TypeHandleDictionary 335.33 100.00
ТипСловник 373,00 111,23
Якщо / Інакше 462,67 137,97
Перемикач 490,33 146,22

Тестування 3 ітерацій з 5 000 000 елементів даних (режим = Випадковий) та 25 типів
Метод Час% від оптимального
TypeHandleDictionary 319.33 100.00
ТипСловник 371,00 116,18
Перемикач 483,00 151,25
Якщо / Інакше 562,00 175,99

Тестування 3 ітерацій з 5 000 000 елементів даних (режим = Випадковий) та 50 типів
Метод Час% від оптимального
TypeHandleDictionary 319.67 100.00
ТипСловник 376,67 117,83
Перемикач 453,33 141,81
Якщо / Інакше 1,032,67 323,04

Принаймні на моїй машині підхід до словника обробників типів перемагає всі інші за будь-що понад 15 різних типів, коли розподіл типів, що використовуються як вхідні дані до методу, є випадковим.

Якщо, з іншого боку, вхідні дані складаються повністю з типу, який перевіряється першим у ланцюжку if / else, цей метод набагато швидший:

Тестування 3 ітерацій з 5 000 000 елементами даних (режим = UniformFirst) та 50 типами
Метод Час% від оптимального
Якщо / Інакше 39,00 100,00
Словник TypeHandleD 317.33 813.68
ТипСловник 396,00 1015,38
Перемикач 403,00 1,033,33

І навпаки, якщо вхідні дані - це завжди остання річ у ланцюжку if / else, це має протилежний ефект:

Тестування 3 ітерацій з 5 000 000 елементами даних (mode = UniformLast) та 50 типами
Метод Час% від оптимального
TypeHandleD Dictionary 317.67 100.00
Перемикач 354,33 111,54
ТипСловник 377,67 118,89
Якщо / Інакше 1 907,67 600,52

Якщо ви можете зробити деякі припущення щодо свого введення, ви можете отримати найкращу продуктивність за допомогою гібридного підходу, коли ви виконуєте перевірку if / else для кількох найбільш поширених типів, а потім повертаєтесь до підходу, керованого словником, якщо вони не вдаються.


Вибачте, я пропустив цю відповідь раніше. Безумовно, заслуговує на висоту.
Дивовижний

8
Це, мабуть, найкраща відповідь на те, що я бачив на SO. Боже мій ... великі пальці!
Ron L

19

По-перше, ви порівнюєте яблука та апельсини. Спочатку потрібно порівняти перемикач типу проти перемикача на рядок, а потім, якщо на тип проти, якщо на рядок, а потім порівняти переможців.

По-друге, саме для цього був розроблений ОО. У мовах, що підтримують OO, увімкнення типу (будь-якого типу) є запахом коду, що вказує на поганий дизайн. Рішення полягає в отриманні загальної бази за допомогою абстрактного або віртуального методу (або подібної конструкції, залежно від вашої мови)

напр.

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

Потім, замість того, щоб робити оператор switch, ви просто викликаєте childNode.Action ()


2
(Поряд із читабельністю та ремонтопридатністю) Цікавим буде питання, наскільки це вдається у порівнянні з двома іншими підходами. Примітка: ви б також взяли до уваги виконання тієї частини, де реалізація Nodeобрана та інстанційована (наприклад, Factory).
Matthijs Wessels

18

Я щойно запровадив програму для швидкого тестування та сформулював її за допомогою ANTS 4.
Spec: .Net 3.5 sp1 у 32-бітовій Windows XP, код побудований у режимі випуску.

3 мільйони тестів:

  • Перемикач: 1,842 секунди
  • Якщо: 0,344 секунди.

Крім того, результати оператора switch виявляють (як не дивно), що довші імена займають більше часу.

1 мільйон тестів

  • Боб: 0,612 секунди.
  • Джилл: 0,835 секунди.
  • Марко: 1,093 секунди.

Мені здається, "Якщо ще" швидше, принаймні той сценарій, який я створив.

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

2
Це має такий сенс, оскільки перемикання здійснюється за допомогою рядків, і ви повинні враховувати накладні витрати на перетворення рядків. Що, якби кожен клас мав перелік?
Рік Мінеріх,

4
"Боб" швидший, тому що він коротший або тому, що перший?
Frank Szczerba

12

Оператор Switch виконується швидше, ніж драбина if-else-if. Це пояснюється здатністю компілятора оптимізувати оператор switch. У випадку сходів if-else-if код повинен обробляти кожен оператор if у порядку, визначеному програмістом. Однак, оскільки кожен випадок у операторі switch не покладається на попередні випадки, компілятор може повторно замовити тестування таким чином, щоб забезпечити найшвидше виконання.


Однак порівняння типів набагато легше підтримувати. - Постарайтеся не передчасно оптимізувати.
Nescio

1
Це корисно сказати, що написане - це не зовсім те, що виконується, але вводить в оману припущення, що ІФ не можна оптимізувати. Я не фахівець з оптимізаторів, але я розглядаю клас у Reflector, де If / ElseIf із змінною лише для читання реалізовано в IL так само, як SWITCH.
Кевін Крумлі,

Конструкція if-else-if дійсно може бути перетворена компілятором у оператор switch. Чому б це не могло?
Тара

6

Якщо у вас зроблені класи, я б запропонував використовувати шаблон дизайну стратегії замість switch або elseif.


Це відмінна пропозиція! Нехай об'єкт сам вирішить, що йому потрібно робити.
Боб Кінг

Це було б набагато елегантніше і врятувало б усі ці фафори. Однак для цього знадобився б великий рефакторинг у цій галузі і використовувався б лише в крайньому випадку. Але я в цілому з вами згоден. : D
Дивовижний

4

Спробуйте використовувати перерахування для кожного об’єкта, ви можете швидко і легко ввімкнути перелічення.


3

Якщо ви вже не написали цього і не виявили, що у вас є проблеми з продуктивністю, я б не хвилювався, що швидше. Ідіть з тим, який є більш читабельним. Пам’ятайте: «Передчасна оптимізація - корінь усього зла». - Дональд Кнут


1
За винятком того, що ми не передчасно оптимізуємо. Ми проводимо оптимізацію публікацій. Вибачте, але -1.
Дивовижний

1
Відповіді тут не лише на користь допитувача, вони призначені для тих, хто прийде пізніше. Люди повинні усвідомити, що приймати це рішення заздалегідь з міркувань ефективності - не найкращий спосіб це зробити.
Кріс Апчерч,

О, чудово, зараз відповідь, яка стверджує, що питання не має значення, є перш за все іншими відповідями, які насправді намагаються відповісти на питання. ШЛЯХ ДО ХЛОПЦІВ! : P
Дивовижний

@Quarrelsome: застереження від передчасної оптимізації є доречним для будь-якого питання оптимізації, якщо ви не вказали, що ви вже профайлювали свій код і визнали його занадто повільним. Помістіть це у своєму питанні, і моя відповідь, мабуть, перестане голосувати.
Кріс Апчерч,

1
Враховуючи, наскільки поширеною є передчасна оптимізація, так.
Кріс Апчерч,

3

Конструкція SWITCH спочатку була призначена для цілочисельних даних; він мав на меті використовувати аргумент безпосередньо як індекс у "таблиці відправлення", таблиці покажчиків. Як такий, існував би один тест, а потім запуск безпосередньо до відповідного коду, а не серія тестів.

Складність тут полягає в тому, що його використання було узагальнено для типів "рядків", які, очевидно, не можуть бути використані як індекс, і вся перевага конструкції SWITCH втрачена.

Якщо швидкість - це ваша ціль, проблема полягає не в коді, а в структурі даних. Якщо простір "імені" настільки простий, як ви його показуєте, краще закодувати його у цілочисельне значення (наприклад, при створенні даних) і використовувати це ціле число у полі "багато разів у повільній частині програми".


3

Якщо типи, які ви вмикаєте, є примітивними .NET-типами, ви можете використовувати Type.GetTypeCode (Тип), але якщо це спеціальні типи, вони всі повернуться як TypeCode.Object.

Словник з представниками або класами обробників також може працювати.

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
}

Милий :) Будь-яка ідея, якщо виклик делегата матиме якісь впливи на продуктивність?
Дивовижний

2

Перемикач () компілюється в код, еквівалентний набору else ifs. Порівняння рядків буде набагато повільнішим, ніж порівняння типів.


Чи не виконує CLR жодних прикольних трюків у операторі switch? Чому ще змушує вас використовувати лише константи як оператори регістру замість змінних, якщо це лише переклад в else ifs?
Дивовижний

CLR буде виконувати прикольні трюки, якщо оператор switch використовує базові типи, однак тут кожен випадок вимагає порівняння рядків, тому для оптимізації мало можливостей.
Moonshadow

1
C # не компілює перемикачі на основі рядків для порівняння рядків. Оскільки мітки регістру мають бути літералами, він використовує такі фокуси, як інтернування переключеної змінної, увімкнення хеш-коду, а потім перевірка ідентичності об’єкта (що буде працювати через інтернування), щоб переконатися, що збіг був правильним.
Michael Burr

oOo зараз це дуже цікаво. Отже, CLR робить виняток для рядків, який не робить для інших типів? То чи означає це, що воно еквівалентне інших ifs?
Дивовижний

2

Я пам’ятаю, як я читав у кількох довідниках, що розгалуження if / else швидше, ніж твердження switch. Однак трохи досліджень на Blackwasp показують, що оператор перемикання насправді швидший: http://www.blackwasp.co.uk/SpeedTestIfElseSwitch.aspx

Насправді, якщо ви порівнюєте типові твердження від 3 до 10 (або близько того), я серйозно сумніваюся, що є реальний приріст продуктивності за допомогою того чи іншого.

Як вже сказав Кріс, перейдіть до читабельності: Що швидше, увімкніть рядок чи ще якщо тип?


2

Я думаю, що основною проблемою продуктивності тут є те, що в блоці перемикача ви порівнюєте рядки, а в блоці if-else ви перевіряєте типи ... Ці два не однакові, і тому я б сказав, що ви це "порівняння картоплі з бананами".

Я б почав із порівняння цього:

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

1
Привіт, дякую за відповідь! Це насправді такий сценарій, який я маю. Ми можемо використовувати або унікальний ідентифікатор (який є рядком), або тип об’єкта для розмежування цих об’єктів.
Дивовижний

2

Я не впевнений, наскільки швидшим може бути правильний дизайн, щоб піти на поліморфізм.

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

Побачити, що робить ваш оператор перемикання, допоможе краще. Якщо ваша функція насправді не стосується дії над типом, можливо, ви можете визначити перелік для кожного типу.

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

Я припускаю, що це має бути швидше, ніж обидва розглянуті підходи. Можливо, ви захочете спробувати абстрактний маршрут класу, якщо наносекунди мають для вас значення .


2

Я створив маленьку консоль, щоб показати своє рішення, лише для того, щоб виділити різницю в швидкості. Я використовував інший алгоритм хеш-рядків, оскільки версія сертифіката для мене є повільною під час виконання, і дублікати малоймовірні, і в такому випадку мій оператор перемикання не вдасться (ніколи не траплявся до цього часу). Мій унікальний метод розширення хешу включений до коду нижче.

Консольний додаток Core 2 з виходом

Я прийму 29 кліків понад 695 кліків у будь-який час, особливо при використанні критичного коду.

За допомогою набору рядків із даної бази даних ви можете створити невелику програму для створення константи у даному файлі, яку ви будете використовувати у своєму коді, якщо значення додаються, ви просто повторно запускаєте свою партію, а константи генеруються та підбираються рішення.

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

        }

0

Порівняння рядків завжди буде повністю покладатися на середовище виконання (за винятком випадків, коли рядки розподілені статично, хоча потреба у порівнянні між собою є спірною). Однак порівняння типів можна здійснити за допомогою динамічного або статичного прив'язки, і в будь-якому випадку це ефективніше для середовища виконання, ніж порівняння окремих символів у рядку.


0

Напевно, перемикач на String компілюється до порівняння String (по одному на кожен випадок), яке є повільнішим за порівняння типів (і набагато повільніше, ніж типове ціле порівняння, яке використовується для switch / case)?


0

Три думки:

1) Якщо ви збираєтеся робити щось інше на основі типів об’єктів, може мати сенс перенести цю поведінку в ці класи. Тоді замість switch або if-else ви просто зателефонуєте childNode.DoSomething ().

2) Порівняння типів буде набагато швидшим, ніж порівняння рядків.

3) У конструкції if-else ви можете скористатися переупорядкуванням тестів. Якщо об'єкти "Джилл" складають 90% об'єктів, що проходять туди, спершу перевіряйте їх.


0

Однією з проблем, що виникають у вас із перемикачем, є використання рядків, таких як "Боб", це спричинить набагато більше циклів і рядків у складеному коді. Створений ІЛ повинен буде оголосити рядок, встановити для нього "Боб", а потім використовувати його для порівняння. Тож маючи це на увазі, ваші оператори IF працюватимуть швидше.

PS. Приклад Aeon не працює, оскільки ви не можете вмикати типи. (Ні, я не знаю, чому саме, але ми спробували це, але це не працює. Це пов’язано з типом, що є змінним)

Якщо ви хочете протестувати це, просто створіть окрему програму та побудуйте два простих методи, які виконують те, що написано вище, і використовують щось на зразок Ildasm.exe, щоб побачити ІЛ. Ви помітите набагато менше рядків у методі виписування IF методу IL.

Ildasm поставляється з VisualStudio ...

Сторінка ILDASM - http://msdn.microsoft.com/en-us/library/f7dy01k1(VS.80).aspx

Підручник з ILDASM - http://msdn.microsoft.com/en-us/library/aa309387(VS.71).aspx


0

Пам’ятайте, профайлер - ваш друг. Будь-які здогадки - це втрата часу більшу частину часу. До речі, я мав хороший досвід роботи з профайлером dotTrace JetBrains .


0

Вмикання рядка в основному компілюється у сходи if-else-if. Спробуйте декомпілювати просту. У будь-якому випадку перевірка рівності рядків повинна бути дешевшою, оскільки вони інтерновані, і все, що потрібно, - це перевірка посилань. Робіть те, що має сенс з точки зору ремонтопридатності; якщо ви складаєте рядки, переключіть рядок. Якщо ви вибираєте, виходячи з типу, більш доречна типова драбина.


0

Я роблю це трохи інакше, рядки, які ви вмикаєте, будуть постійними, тому ви можете передбачити значення під час компіляції.

у вашому випадку я б використовував хеш-значення, це перемикач int, у вас є 2 варіанти, використовуйте константи часу компіляції або обчислюйте під час виконання.

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

Метод розширення для GetUniquetHashCode може бути приблизно таким:

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


    }

Джерело цього коду було опубліковано тут. Зверніть увагу, що використання криптографії є ​​повільним, ви зазвичай розігріваєте підтримуваний рядок під час запуску програми, я роблю це, зберігаючи їх у статичних полях, оскільки вони не змінюються та не мають значення для екземпляру. зауважте, що я встановив значення тегу об’єкта вузла, я міг використати будь-яку властивість або додати її, лише переконайтесь, що вони синхронізуються з фактичним текстом.

Я працюю в системах із низькою затримкою, і всі мої коди подаються у вигляді рядка command: value, command: value ....

тепер усі команди відомі як 64-бітові цілочисельні значення, тому таке перемикання економить деякий час процесора.


0

Я просто читав тут список відповідей і хотів поділитися цим контрольним тестом, який порівнює switchконструкцію з операторами if-elseта і тернарними ?.

Мені подобається в цій публікації те , що вона порівнює не лише одноліві конструкції (наприклад, if-else), але конструкції подвійного та потрійного рівня (наприклад, if-else-if-else).

За результатами, if-elseконструкція була найшвидшою у 8/9 тестових випадках; switchконструкція прив'язаний для найшвидших в 5/9 тестів.

Отже, якщо ви шукаєте швидкість, це, if-elseздається, найшвидший шлях.


-1

Можливо, мені чогось не вистачає, але чи не могли б ви зробити оператор switch для типу замість рядка? Це,

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

Ні, операція перемикання не працює на таких об’єктах, як типи. Тільки "інтегральні типи"
хиткий

Рядок не є інтегральним типом!
Moonshadow

Тоді чому Framework допускає рядок, а не тип із помилкою компілятора: "Очікується значення інтегрального типу". Це лише невелика хитрість, щоб дозволити вам використовувати рядки, хоча вони не є цілісними типами?
Дивовижний

Ох Гаразд, вибачте :) Я не знаю c #, здавалося б логічним дозволити речі, які є дійсними як умова if, бути умовою перемикання.
Aeon

@Quibblesome це справді фокус. switch case може обробляти рядок, хоча вони не є цілісними типами
nawfal
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.