c# - valid - ¿Qué es más rápido, activar string o else en type?
remarks c# (23)
Digamos que tengo la opción de identificar una ruta de código para tomar en base a una comparación de cadenas o si prefiero el tipo:
¿Cuál es más rápido y por qué?
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)
{
}
Actualización: La razón principal por la que pregunto esto es porque la declaración de cambio es perculiar acerca de lo que cuenta como un caso. Por ejemplo, no le permitirá usar variables, solo constantes que se mueven al ensamblaje principal. Supuse que tenía esta restricción debido a algunas cosas funky que estaba haciendo. Si solo está traduciendo a elseifs (como comentó un póster), ¿por qué no se nos permiten variables en las declaraciones de casos?
Advertencia: estoy post-optimizando. Este método se llama muchas veces en una parte lenta de la aplicación.
A menos que ya haya escrito esto y descubra que tiene un problema de rendimiento, no me preocuparía por cuál es más rápido. Ve con el que es más legible. Recuerde: "La optimización prematura es la raíz de todo mal". - Donald Knuth
Acabo de implementar una aplicación de prueba rápida y la perfilé con ANTS 4.
Spec: .Net 3.5 sp1 en Windows XP de 32 bits, código incorporado en modo de lanzamiento.
3 millones de pruebas:
- Cambiar: 1.842 segundos
- Si: 0.344 segundos.
Además, los resultados de la declaración de cambio revelan (como era de esperar) que los nombres más largos tardan más.
1 millón de pruebas
- Bob: 0.612 segundos.
- Jill: 0.835 segundos.
- Marko: 1.093 segundos.
Parece que el "Si es necesario" es más rápido, al menos el escenario que creé.
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";}}
Creé una pequeña consola para mostrar mi solución, solo para resaltar la diferencia de velocidad. Utilicé un algoritmo hash de cadena diferente, ya que la versión del certificado se ralentiza en tiempo de ejecución y los duplicados son poco probables y, en ese caso, mi instrucción de conmutación fallaría (nunca ha sucedido hasta ahora). Mi método exclusivo de extensión hash está incluido en el código a continuación.
Tomaré 29 ticks sobre 695 ticks en cualquier momento, especialmente cuando uso un código crítico.
Con un conjunto de cadenas de una base de datos dada, puede crear una pequeña aplicación para crear la constante en un archivo dado para que pueda usar en su código; si se agregan valores, simplemente vuelva a ejecutar el lote y las constantes se generan y recogen la solución.
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();
}
Creo que el principal problema de rendimiento aquí es que en el bloque de conmutación, se comparan las cadenas, y que en el bloque if-else, se comprueban los tipos ... Esos dos no son lo mismo, y por lo tanto, te diría ''re'' comparando patatas con plátanos ''.
Comenzaría comparando esto:
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")
{}
El conmutador () compilará un código equivalente a un conjunto de if ifs. Las comparaciones de cadenas serán mucho más lentas que las comparaciones de tipos.
En primer lugar, estás comparando manzanas y naranjas. En primer lugar, debe comparar el tipo de interruptor con el de la cadena y, luego, el del tipo vs si está en la cadena, y luego comparar los ganadores.
En segundo lugar, este es el tipo de cosas para las que OO fue diseñado. En idiomas que admiten OO, el tipo de encendido (de cualquier tipo) es un olor de código que apunta a un diseño deficiente. La solución es derivar de una base común con un método abstracto o virtual (o una construcción similar, dependiendo de su idioma)
p.ej.
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
}
}
Entonces, en lugar de hacer la declaración de cambio, simplemente llama a childNode.Action ()
Intente usar enumeraciones para cada objeto, puede activar las enumeraciones de manera rápida y fácil.
La comparación de cadenas siempre dependerá completamente del entorno de tiempo de ejecución (a menos que las cadenas estén asignadas estáticamente, aunque la necesidad de compararlas entre sí es discutible). Sin embargo, la comparación de tipos se puede realizar a través de un enlace dinámico o estático, y de cualquier forma es más eficiente para el entorno de tiempo de ejecución que la comparación de caracteres individuales en una cadena.
La instrucción Switch es más rápida de ejecutar que la escalera if-else-if. Esto se debe a la capacidad del compilador de optimizar la declaración de cambio. En el caso de la escalera if-else-if, el código debe procesar cada sentencia if en el orden determinado por el programador. Sin embargo, debido a que cada caso dentro de una declaración de cambio no se basa en casos anteriores, el compilador puede volver a ordenar las pruebas de tal manera que proporcione la ejecución más rápida.
Los resultados del perfil de Greg son geniales para el escenario exacto que cubrió, pero curiosamente, los costos relativos de los diferentes métodos cambian drásticamente cuando se consideran varios factores diferentes, incluyendo la cantidad de tipos que se comparan y la frecuencia relativa y cualquier patrón en los datos subyacentes .
La respuesta simple es que nadie puede decirle cuál será la diferencia de rendimiento en su escenario específico, tendrá que medir el rendimiento de diferentes maneras en su propio sistema para obtener una respuesta precisa.
La cadena If / Else es un enfoque efectivo para un pequeño número de comparaciones de tipos, o si puede predecir de manera fiable qué pocos tipos conformarán la mayoría de los que ve. El problema potencial con el enfoque es que a medida que aumenta el número de tipos, también aumenta el número de comparaciones que deben ejecutarse.
si ejecuto lo siguiente:
int value = 25124;
if(value == 0) ...
else if (value == 1) ...
else if (value == 2) ...
...
else if (value == 25124) ...
cada una de las condiciones previas debe evaluarse antes de ingresar el bloque correcto. Por otra parte
switch(value) {
case 0:...break;
case 1:...break;
case 2:...break;
...
case 25124:...break;
}
realizará un simple salto al código correcto.
Donde se vuelve más complicado en su ejemplo es que su otro método usa un interruptor en cadenas en lugar de enteros, lo que se vuelve un poco más complicado. En un nivel bajo, las cadenas no se pueden activar de la misma forma que los valores enteros, por lo que el compilador C # puede hacer algo para que esto funcione.
Si la instrucción switch es "lo suficientemente pequeña" (donde el compilador hace lo que cree que es mejor automáticamente), al encender las cadenas se genera un código que es igual que una cadena if / else.
switch(someString) {
case "Foo": DoFoo(); break;
case "Bar": DoBar(); break;
default: DoOther; break;
}
es lo mismo que:
if(someString == "Foo") {
DoFoo();
} else if(someString == "Bar") {
DoBar();
} else {
DoOther();
}
Una vez que la lista de elementos en el diccionario sea "lo suficientemente grande", el compilador creará automáticamente un diccionario interno que correlacione desde las cadenas en el interruptor a un índice entero y luego un interruptor basado en ese índice.
Se ve algo como esto (imagina más entradas de las que me voy a molestar en escribir)
Un campo estático se define en una ubicación "oculta" que está asociada con la clase que contiene la instrucción switch de tipo Dictionary<string, int>
y se le da un nombre mutilado
//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();
}
En algunas pruebas rápidas que acabo de ejecutar, el método If / Else es aproximadamente 3 veces más rápido que el cambio para 3 tipos diferentes (donde los tipos se distribuyen aleatoriamente). En 25 tipos, el interruptor es más rápido por un pequeño margen (16%) en 50 tipos, el interruptor es más del doble de rápido.
Si va a activar una gran cantidad de tipos, sugeriría un tercer método:
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...
}
}
Esto es similar a lo sugerido por Ted Elliot, pero el uso de identificadores de tipo de tiempo de ejecución en lugar de objetos de tipo completo evita la sobrecarga de cargar el objeto tipo a través de la reflexión.
Aquí hay algunos tiempos rápidos en mi máquina:
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
En mi máquina, al menos, el enfoque del diccionario tipo maneja supera a todos los demás para algo más de 15 tipos diferentes cuando la distribución de los tipos utilizados como entrada al método es aleatoria.
Si, por otro lado, la entrada se compone completamente del tipo que se verifica primero en la cadena if / else, ese método es mucho más rápido:
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
Por el contrario, si la entrada es siempre lo último en la cadena if / else, tiene el efecto opuesto:
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
Si puede hacer algunas suposiciones sobre su entrada, puede obtener el mejor rendimiento de un enfoque híbrido en el que realiza verificaciones if / else para los pocos tipos que son más comunes, y luego recurrir a un enfoque basado en el diccionario si estos fallan.
No estoy seguro de qué tan rápido podría ser el diseño correcto sería ir por el polimorfismo.
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();
}
Ver lo que hace su declaración de cambio lo ayudará mejor. Si su función no es realmente nada acerca de una acción en el tipo, puede definir una enumeración en cada tipo.
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();
}
}
Supongo que esto tiene que ser más rápido que ambos enfoques en cuestión. Es posible que desee probar la ruta de la clase abstracta si los nanosegundos son importantes para usted .
Recuerda, el generador de perfiles es tu amigo. Cualquier conjetura es una pérdida de tiempo la mayor parte del tiempo. Por cierto, he tenido una buena experiencia con el perfilador dotTrace JetBrains.
Recuerdo haber leído en varios libros de referencia que la bifurcación if / else es más rápida que la instrucción switch. Sin embargo, un poco de investigación sobre Blackwasp muestra que la declaración de cambio es realmente más rápida: http://www.blackwasp.co.uk/SpeedTestIfElseSwitch.aspx
En realidad, si comparas las típicas declaraciones de 3 a 10 (más o menos), dudo seriamente que haya alguna ganancia de rendimiento real usando una u otra.
Como ya ha dicho Chris, busque legibilidad: ¿qué es más rápido, encienda la cadena o else en el tipo?
Seguramente, el cambio en String se compilaría hasta una comparación de cadenas (una por caso) que es más lenta que una comparación de tipos (y mucho más lenta que la comparación entera típica que se usa para el cambio / caso).
Si los tipos que está activando son tipos .NET primitivos, puede usar Type.GetTypeCode (Type), pero si son tipos personalizados, todos volverán como TypeCode.Object.
Un diccionario con delegados o clases de controladores también podría funcionar.
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
}
Si tienes las clases creadas, te sugiero usar un patrón de diseño de estrategia en lugar de cambiar o elseif.
Tres pensamientos:
1) Si vas a hacer algo diferente según los tipos de objetos, podría tener sentido mover ese comportamiento a esas clases. Luego, en lugar de cambiar o si no, simplemente llamaría a childNode.DoSomething ().
2) Comparar tipos será mucho más rápido que las comparaciones de cadenas.
3) En el diseño if-else, es posible que pueda aprovechar el reordenamiento de las pruebas. Si los objetos "Jill" constituyen el 90% de los objetos que pasan por allí, pruébelos primero.
Un constructo SWITCH fue originalmente pensado para datos enteros; su intención era usar el argumento directamente como un índice en una "tabla de despacho", una tabla de punteros. Como tal, habría una prueba única, luego se lanzaría directamente al código relevante, en lugar de una serie de pruebas.
La dificultad aquí es que su uso se ha generalizado a tipos de "cadenas", que obviamente no se pueden usar como índice, y todas las ventajas de la construcción SWITCH se pierden.
Si su objetivo previsto es la velocidad, el problema NO es su código, sino su estructura de datos. Si el espacio "nombre" es tan simple como lo muestra, mejor codificarlo en un valor entero (cuando se crean datos, por ejemplo), y usar este entero en "muchas veces en una parte lenta de la aplicación".
Uno de los problemas que tiene con el interruptor es usar cadenas, como "Bob", esto causará muchos más ciclos y líneas en el código compilado. El IL que se genera tendrá que declarar una cadena, configurarlo en "Bob" y luego usarlo en la comparación. Entonces con eso en mente, sus declaraciones IF se ejecutarán más rápido.
PD. El ejemplo de Aeon no funcionará porque no puede activar Tipos. (No, no sé exactamente por qué, pero lo hemos intentado y no funciona. Tiene que ver con que el tipo sea variable)
Si desea probar esto, solo cree una aplicación por separado y cree dos métodos simples que hagan lo que se escribió arriba y use algo como Ildasm.exe para ver el IL. Notarás muchas menos líneas en la instrucción IF. Método IL.
Ildasm viene con VisualStudio ...
Página de ILDASM - http://msdn.microsoft.com/en-us/library/f7dy01k1(VS.80).aspx
Tutorial de ILDASM - http://msdn.microsoft.com/en-us/library/aa309387(VS.71).aspx
I kind of do it a bit different, The strings you''re switching on are going to be constants, so you can predict the values at compile time.
in your case i''d use the hash values, this is an int switch, you have 2 options, use compile time constants or calculate at run-time.
//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;
}
}
The extension method for GetUniquetHashCode can be something like this:
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);
}
}
The source of this code was published here Please note that using Cryptography is slow, you would typically warm-up the supported string on application start, i do this my saving them at static fields as will not change and are not instance relevant. 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 may be missing something, but couldn''t you do a switch statement on the type instead of the String? That is,
switch(childNode.Type)
{
case Bob:
break;
case Jill:
break;
case Marko:
break;
}
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 ?
operators.
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.
Switch on string basically gets compiled into a if-else-if ladder. Try decompiling a simple one. In any case, testing string equailty should be cheaper since they are interned and all that would be needed is a reference check. Do what makes sense in terms of maintainability; if you are compring strings, do the string switch. If you are selecting based on type, a type ladder is the more appropriate.