Что быстрее, включите строку или elseif по типу?
Допустим, у меня есть возможность идентифицировать путь кода, который будет использоваться на основе сравнения строк, или iffing типа:
Что быстрее и почему?
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?
- Производительность вложенного урожая в дереве
- Проверьте, имеет ли свойство атрибут
- Преимущества использования частных статических методов
- Сделайте первую букву верхнего строчка строки (с максимальной производительностью)
- Самый эффективный способ вставки строк в базу данных MySQL
Предостережение: я пост-оптимизирую. Этот метод называется много раз в медленной части приложения.
Результаты профиля Грега отлично подходят для точного сценария, который он освещал, но интересно, что относительные затраты на различные методы резко меняются при рассмотрении ряда различных факторов, включая количество сравниваемых типов, а также относительную частоту и любые шаблоны в базовых данных ,
Простой ответ заключается в том, что никто не может сказать вам, что разница в производительности будет в вашем конкретном сценарии, вам нужно будет измерить производительность по-разному в своей собственной системе, чтобы получить точный ответ.
Цепочка If / Else является эффективным подходом для небольшого количества сопоставлений типов или если вы можете надежно предсказать, какие из немногих типов составят большинство из тех, которые вы видите. Потенциальная проблема подхода заключается в том, что по мере увеличения числа типов увеличивается количество сравнений, которые должны выполняться.
если я выполняю следующее:
int value = 25124; if(value == 0) ... else if (value == 1) ... else if (value == 2) ... ... else if (value == 25124) ...
каждый из предыдущих условий if должен быть оценен до ввода правильного блока. С другой стороны
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(); }
После того, как список элементов в словаре станет «достаточно большим», компилятор автоматически создаст внутренний словарь, который отображает из строк в коммутаторе на целочисленный индекс, а затем на основе этого индекса.
Это выглядит примерно так (просто представьте себе больше записей, чем я буду писать)
Статическое поле определяется в «скрытом» местоположении, которое связано с classом, содержащим оператор switch типа Dictionary
и заданное искаженное имя
//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(); 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 TypeHandleSwitcher = CreateSwitcher(); private static Dictionary CreateSwitcher() { var ret = new Dictionary(); 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 типов Метод Время% оптимального If / Else 179.67 100.00 ТипHandleDictionary 321.33 178.85 ТипDictionary 377.67 210.20 Переключатель 492.67 274.21 Тестирование 3 итераций с 5 000 000 элементов данных (режим = случайный) и 10 типов Метод Время% оптимального Если / Else 271.33 100.00 ТипHandleDictionary 312.00 114.99 ТипDictionary 374.33 137.96 Переключатель 490.33 180.71 Тестирование 3 итераций с 5 000 000 элементов данных (режим = случайный) и 15 типов Метод Время% оптимального ТипHandleDictionary 312.00 100.00 Если / Else 369.00 118.27 ТипDictionary 371.67 119.12 Переключатель 491,67 157,59 Тестирование 3 итераций с 5 000 000 элементов данных (режим = случайный) и 20 типов Метод Время% оптимального ТипHandleDictionary 335.33 100.00 ТипDictionary 373.00 111.23 Если / Else 462.67 137.97 Переключатель 490.33 146.22 Тестирование 3 итераций с 5 000 000 элементов данных (режим = случайный) и 25 типов Метод Время% оптимального ТипHandleDictionary 319.33 100.00 ТипDictionary 371.00 116.18 Переключатель 483,00 151,25 If / Else 562.00 175.99 Тестирование 3 итераций с 5 000 000 элементов данных (режим = случайный) и 50 типов Метод Время% оптимального ТипHandleDictionary 319.67 100.00 ТипDictionary 376.67 117.83 Переключатель 453,33 141,81 If / Else 1,032.67 323.04
На моей машине, по крайней мере, подход словаря типа handle превосходит всех остальных для чего-либо более 15 различных типов, когда распределение типов, используемых в качестве входных данных для метода, является случайным.
Если, с другой стороны, вход полностью состоит из типа, который сначала проверяется в цепочке if / else, этот метод выполняется намного быстрее:
Тестирование 3 итераций с 5 000 000 элементов данных (режим = UniformFirst) и 50 типов Метод Время% оптимального If / Else 39.00 100.00 ТипHandleDictionary 317.33 813.68 ТипDictionary 396.00 1 015.38 Переключатель 403,00 1,033,33
И наоборот, если вход всегда является последним в цепочке if / else, он имеет противоположный эффект:
Тестирование 3 итераций с 5 000 000 элементов данных (режим = UniformLast) и 50 типов Метод Время% оптимального ТипHandleDictionary 317.67 100.00 Переключатель 354.33 111.54 ТипDictionary 377.67 118.89 If / Else 1,907.67 600.52
Если вы можете сделать некоторые предположения о своем вводе, вы можете получить максимальную производительность от гибридного подхода, когда вы выполняете if / else проверку нескольких типов, которые наиболее распространены, а затем возвращаются к подходу, основанному на словарях, если они не работают.
Я просто внедрил приложение для быстрого тестирования и профилировал его с помощью ANTS 4.
Spec: .Net 3.5 sp1 в 32-битной Windows XP, код построен в режиме выпуска.
3 миллиона тестов:
- Переключатель: 1,842 секунды
- Если: 0.344 секунды.
Кроме того, результаты оператора switch показывают (неудивительно), что более длинные имена занимают больше времени.
1 миллион тестов
- Боб: 0,612 секунды.
- Джилл: 0,835 секунды.
- Марко: 1.093 секунды.
Похоже, что «If Else» быстрее, по крайней мере, тот сценарий, который я создал.
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";}}
Во-первых, вы сравниваете яблоки и апельсины. Вам сначала нужно сравнить switch on type vs switch on string, а затем if on type vs if on string, а затем сравнить победителей.
Во-вторых, это то, для чего предназначался OO. В языках, поддерживающих 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 ()
Оператор switch быстрее выполняется, чем лестница if-else-if. Это связано с возможностью компилятора оптимизировать оператор switch. В случае лестницы if-else-if код должен обрабатывать каждую инструкцию if в порядке, определенном программистом. Однако, поскольку каждый случай в выражении switch не полагается на более ранние случаи, компилятор может повторно заказать тестирование таким образом, чтобы обеспечить самое быстрое выполнение.
Если у вас есть classы, я бы предложил использовать шаблон страtagsи Strategy вместо switch или elseif.
Попробуйте использовать enums для каждого объекта, вы можете быстро и легко включить enums.
Если вы уже не писали это и не обнаружили проблемы с производительностью, я бы не стал беспокоиться о том, что происходит быстрее. Пойдите с тем, что более читаемо. Помните: «Преждевременная оптимизация – это корень всего зла». – Дональд Кнут
Конструкция SWITCH изначально предназначалась для целочисленных данных; он намерен использовать аргумент напрямую как индекс в «таблицу рассылки», таблицу указателей. Таким образом, был бы один тест, а затем запускаться непосредственно к соответствующему коду, а не серия тестов.
Трудность здесь заключается в том, что ее использование было обобщено на «строковые» типы, которые, очевидно, не могут использоваться как индекс, и все преимущества конструкции SWITCH теряются.
Если скорость – это ваша намеченная цель, проблема не в вашем коде, а в вашей структуре данных. Если пространство имен так просто, как вы его показываете, лучше закодировать его в целочисленное значение (например, когда создаются данные) и использовать это целое число «во много раз в медленной части приложения».
Если типы, которые вы используете, являются примитивными типами .NET, вы можете использовать Type.GetTypeCode (Type), но если они являются настраиваемыми типами, все они будут возвращаться как TypeCode.Object.
Также может работать словарь с делегатами или classами обработчиков.
Dictionary handlers = new Dictionary(); 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 }
Коммутатор () будет компилировать код, эквивалентный набору else ifs. Сравнение строк будет намного медленнее, чем сравнение типов.
Я вспоминаю чтение в нескольких справочниках, что ветвление if / else быстрее, чем оператор switch. Тем не менее, немного исследований в Blackwasp показывает, что оператор switch на самом деле быстрее: http://www.blackwasp.co.uk/SpeedTestIfElseSwitch.aspx
В действительности, если вы сравниваете типичные заявления от 3 до 10 (или около того), я серьезно сомневаюсь, что есть реальная прибыль от использования одного или другого.
Как сказал Крис, продолжайте читать: что быстрее, включите строку или elseif по типу?
Я думаю, что главная проблема с производительностью здесь заключается в том, что в блоке switch вы сравниваете строки, а в блоке 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") {}
Я не уверен, насколько быстрее это может быть правильный дизайн для polymorphismа.
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(); }
Видеть, что делает ваш оператор switch, поможет лучше. Если ваша функция не является чем-то вроде действия над типом, возможно, вы можете определить перечисление для каждого типа.
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(); } }
Я предполагаю, что это должно быть быстрее, чем оба подхода. Вы можете попробовать абстрактный маршрут classа, если вам понадобятся наносекунды .
Я создал небольшую консоль, чтобы показать свое решение, просто чтобы выделить разницу в скорости. Я использовал другой алгоритм хеширования строки, поскольку версия сертификата замедляет работу во время выполнения, а дубликаты маловероятны, и если это произойдет, мое заявление о переключении завершится неудачно (до сих пор не было). Мой уникальный метод расширения hashа включен в код ниже.
Я буду принимать 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(); }
Сравнение строк всегда будет полностью полагаться на среду выполнения (если строки не статически распределены, хотя необходимость сравнивать их друг с другом является дискуссионной). Однако сравнение типов может быть выполнено посредством динамического или статического связывания, и в любом случае это более эффективно для среды выполнения, чем сравнение отдельных символов в строке.
Разумеется, коммутатор на String будет скомпилирован до сравнения строк (по одному на случай), который медленнее, чем сравнение типов (и гораздо медленнее, чем типичное целочисленное сравнение, используемое для switch / case)?
Три мысли:
1) Если вы собираетесь делать что-то другое, основанное на типах объектов, может возникнуть смысл переместить это поведение в эти classы. Затем вместо переключателя или if-else вы просто вызываете childNode.DoSomething ().
2) Сравнение типов будет намного быстрее, чем сравнение строк.
3) В дизайне if-else вы, возможно, сможете воспользоваться переупорядочением тестов. Если объекты «Джилл» составляют 90% объектов, проходящих там, сначала проверьте их.
Одна из проблем, возникающих с коммутатором, заключается в использовании строк, таких как «Боб», это приведет к гораздо большему количеству циклов и строк в скомпилированном коде. ИЛ, который сгенерирован, должен будет объявить строку, установить ее в «Боб», а затем использовать ее в сравнении. Поэтому, имея в виду, ваши операторы IF будут работать быстрее.
PS. Пример Aeon не работает, потому что вы не можете включать типы. (Нет, я не знаю, почему именно, но мы пробовали это, но это не работает. Это связано с тем, что тип является переменной)
Если вы хотите проверить это, просто создайте отдельное приложение и создайте два простых метода, которые сделают то, что написано выше, и используйте что-то вроде Ildasm.exe, чтобы увидеть IL. Вы заметите намного меньше строк в IL-методе IF.
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
Помните, что профилировщик – ваш друг. Любое догадки – это пустая трата времени большую часть времени. Кстати, у меня был хороший опыт работы с профайлером dotTrace от JetBrains.
Включение строки в основном скомпилируется в лестницу if-else-if. Попробуйте декомпилировать простой. В любом случае, проверка верности строк должна быть более дешевой, так как они интернированы, и все, что потребуется, – это контрольная проверка. Сделайте то, что имеет смысл с точки зрения ремонтопригодности; если вы компилируете строки, выполните строковый переключатель. Если вы выбираете на основе типа, более подходящей является лестница типа.
Я вроде немного поменяю. Строки, которые вы включаете, будут постоянными, поэтому вы можете предсказать значения во время компиляции.
в вашем случае я бы использовал hash-значения, это 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 { /// /// Return unique Int64 value for input string /// /// /// 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); } }
Источник этого кода был опубликован здесь. Обратите внимание, что использование криптографии выполняется медленно, вы обычно прогреваете поддерживаемую строку при запуске приложения, я делаю это, сохраняя их при статических полях, так как они не изменятся и не будут иметь значение для экземпляра. please note that I set the tag value of the node object, I could use any property or add one, just make sure that these are in sync with the actual text.
I work on low latency systems and all my codes come as a string of command:value,command:value….
now the command are all known as 64 bit integer values so switching like this saves some CPU time.
I was just reading through the list of answers here, and wanted to share this benchmark test which compares the switch
construct with the if-else
and ternary ?
операторы.
What I like about that post is it not only compares single-left constructs (eg, if-else
) but double and triple level constructs (eg, if-else-if-else
).
According to the results, the if-else
construct was the fastest in 8/9 test cases; the switch
construct tied for the fastest in 5/9 test cases.
So if you’re looking for speed if-else
appears to be the fastest way to go.
I may be missing something, but couldn’t you do a switch statement on the type instead of the String? То есть,
switch(childNode.Type) { case Bob: break; case Jill: break; case Marko: break; }