Результаты профиля Грега великолепны для точного сценария, который он охватил, но, что интересно, относительные затраты на различные методы резко меняются при рассмотрении ряда различных факторов, включая число сравниваемых типов, а также относительную частоту и любые шаблоны в базовые данные.
Простой ответ заключается в том, что никто не может сказать вам, какой будет разница в производительности в вашем конкретном сценарии, вам нужно будет по-разному измерить производительность в вашей собственной системе, чтобы получить точный ответ.
Цепочка 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();
}
Как только список элементов в словаре становится "достаточно большим", компилятор автоматически создает внутренний словарь, который сопоставляет строки в переключателе с целочисленным индексом, а затем переключается на основе этого индекса.
Это выглядит примерно так (просто представьте больше записей, чем я собираюсь напечатать)
Статическое поле определено в «скрытом» месте, которое связано с классом, содержащим оператор switch типа 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 типов коммутатор более чем в два раза быстрее.
Если вы собираетесь переключать большое количество типов, я бы предложил третий способ:
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...
}
}
Это похоже на то, что предложил Тед Эллиот, но использование дескрипторов типа времени выполнения вместо объектов полного типа позволяет избежать накладных расходов при загрузке объекта типа посредством отражения.
Вот несколько быстрых моментов на моей машине:
Testing 3 iterations with 5,000,000 data elements (mode=Random) and 5 types
Method Time % of optimal
If/Else 179.67 100.00
TypeHandleDictionary 321.33 178.85
TypeDictionary 377.67 210.20
Switch 492.67 274.21
Testing 3 iterations with 5,000,000 data elements (mode=Random) and 10 types
Method Time % of optimal
If/Else 271.33 100.00
TypeHandleDictionary 312.00 114.99
TypeDictionary 374.33 137.96
Switch 490.33 180.71
Testing 3 iterations with 5,000,000 data elements (mode=Random) and 15 types
Method Time % of optimal
TypeHandleDictionary 312.00 100.00
If/Else 369.00 118.27
TypeDictionary 371.67 119.12
Switch 491.67 157.59
Testing 3 iterations with 5,000,000 data elements (mode=Random) and 20 types
Method Time % of optimal
TypeHandleDictionary 335.33 100.00
TypeDictionary 373.00 111.23
If/Else 462.67 137.97
Switch 490.33 146.22
Testing 3 iterations with 5,000,000 data elements (mode=Random) and 25 types
Method Time % of optimal
TypeHandleDictionary 319.33 100.00
TypeDictionary 371.00 116.18
Switch 483.00 151.25
If/Else 562.00 175.99
Testing 3 iterations with 5,000,000 data elements (mode=Random) and 50 types
Method Time % of optimal
TypeHandleDictionary 319.67 100.00
TypeDictionary 376.67 117.83
Switch 453.33 141.81
If/Else 1,032.67 323.04
На моем компьютере, по крайней мере, подход словаря дескрипторов типов превосходит все остальные для более чем 15 различных типов при распределении
из типов, используемых в качестве входных данных для метода, является случайным.
Если, с другой стороны, вход полностью состоит из типа, который сначала проверяется в цепочке if / else, этот метод на намного быстрее:
Testing 3 iterations with 5,000,000 data elements (mode=UniformFirst) and 50 types
Method Time % of optimal
If/Else 39.00 100.00
TypeHandleDictionary 317.33 813.68
TypeDictionary 396.00 1,015.38
Switch 403.00 1,033.33
И наоборот, если вход всегда является последним в цепочке if / else, он имеет противоположный эффект:
Testing 3 iterations with 5,000,000 data elements (mode=UniformLast) and 50 types
Method Time % of optimal
TypeHandleDictionary 317.67 100.00
Switch 354.33 111.54
TypeDictionary 377.67 118.89
If/Else 1,907.67 600.52
Если вы можете сделать некоторые предположения относительно своего ввода, вы можете получить лучшую производительность от гибридного подхода, когда вы выполняете, если / else проверяет несколько наиболее распространенных типов, а затем возвращаетесь к подходу на основе словаря, если те терпят неудачу.