Результати профілю Грега чудово підходять для точного сценарію, який він охопив, але що цікаво, відносні витрати різних методів різко змінюються, враховуючи низку різних факторів, включаючи кількість типів, що порівнюються, і відносну частоту та будь-які закономірності в базових даних .
Проста відповідь полягає в тому, що ніхто не може сказати вам, якою буде різниця в продуктивності у вашому конкретному сценарії; вам потрібно буде виміряти продуктивність по-різному самостійно у вашій власній системі, щоб отримати точну відповідь.
Мережа 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>
і надане зіпсоване ім'я
if(theDictionary == null) {
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
{
}
}
Це схоже на те, що запропонував Тед Елліот, але використання дескрипторів типу виконання замість об’єктів повного типу дозволяє уникнути накладних витрат на завантаження об’єкта типу шляхом відображення.
Ось короткі терміни на моїй машині:
Тестування 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 для кількох найбільш поширених типів, а потім повертаєтесь до підходу, керованого словником, якщо вони не вдаються.