Hace unos días, se publicaron N artículos que decían que c#/.net es demasiado lento y exigían que se eliminaran algunas características de c#/.net.
Independientemente de esos artículos, parece ser una regla férrea reconocida por la industria que c#/.net es lento. No importa cómo todos demuestren que c#/.net no es mucho más lento que c++, el rendimiento a nivel de aplicación sigue siendo muy lento. .
Entonces, ¿dónde es lento c#/.net?
Desafortunadamente, la mayoría de los programadores ralentizan la mayoría de los programas C#. Esta conclusión puede no ser fácil de aceptar, pero está muy extendida.
Operaciones de cadena
Casi todos los programas tienen operaciones de cadena y al menos el 90% de ellos deben ignorar las comparaciones de casos. Verifique el código. Al menos la mitad de ellos tienen un código similar a este.
si (str1.ToUpper() == str2.ToUpper())
O la versión ToLower, incluso vi que hay un Web HttpModule que dice:
para (int i = 0; i < strs.Count; i++)
si (valor.ToUpper() == strs[i].ToUpper())
//...
Piénselo, cada vez que se solicita una página, se debe ejecutar dicho código para crear instancias de cadena en grandes extensiones. Lo que es aún más exagerado es que algunas personas dicen que esto es intercambiar espacio por tiempo. . .
Pruebas de rendimiento
Si se dice que este método es lento, es posible que algunas personas no lo admitan y piensen que es el mejor método, por lo que aquí debemos utilizar pruebas específicas para demostrar este hecho.
Primero prepare un método para probar el rendimiento:
TResult estático privado MeasurePerformance
{
GC.Recoger();
int gc0 = GC.CollectionCount(0);
int gc1 = GC.CollectionCount(1);
int gc2 = GC.CollectionCount(2);
Resultado TResult = predeterminado(TResult);
Cronómetro sw = Stopwatch.StartNew();
para (int i = 0; i < bucle; i++)
{
resultado = func(arg);
}
Console.WriteLine(sw.ElapsedMillisegundos.ToString() + "ms");
Console.WriteLine("GC 0:" + (GC.CollectionCount(0) - gc0).ToString());
Console.WriteLine("GC 1:" + (GC.CollectionCount(1) - gc1).ToString());
Console.WriteLine("GC 2:" + (GC.CollectionCount(2) - gc2).ToString());
resultado de devolución;
}
Luego prepare una cadena de montón:
Lista estática privada
{
Lista
char[] chs = nuevo char[3];
para (int i = 0; i < 10000; i++)
{
int j = yo;
para (int k = 0; k < chs.Longitud; k++)
{
chs[k] = (char)('a' + j % 26);
j = j/26;
}
strs.Add(nueva cadena(chs));
}
devolver cadenas;
}
Entonces echemos un vistazo a la implementación de ToUpper:
bool estático privado ImplementByToUpper(List
{
para (int i = 0; i < strs.Count; i++)
si (valor.ToUpper() == cadenas[i].ToUpper())
devolver verdadero;
devolver falso;
}
Finalmente prepare el método principal:
Lista
resultado booleano;
Console.WriteLine("Usar ImplementByToUpper");
resultado = MeasurePerformance(s => ImplementByToUpper(strs, s), "yZh", 1000);
Console.WriteLine("el resultado es " + resultado.ToString());
Consola.ReadLine();
Echemos un vistazo a los resultados de la ejecución:
Utilice ImplementByToUpper
2192ms
GC 0:247
CG 1:0
CG 2:0
el resultado es verdadero
Hagamos una prueba comparativa y usemos string.Equals para probar:
bool estático privado ImplementByStringEquals(List
{
para (int i = 0; i < strs.Count; i++)
if (string.Equals(valor, strs[i], StringComparison.CurrentCultureIgnoreCase))
devolver verdadero;
devolver falso;
}
Echemos un vistazo a los resultados de la ejecución:
Utilice ImplementByStringEquals
1117ms
GC 0:0
CG 1:0
CG 2:0
el resultado es verdadero
En comparación, usar ToUpper es dos veces más lento y tiene muchos objetos basura de generación 0. Quienes afirman intercambiar espacio por tiempo pueden reflexionar sobre esto. ¿Qué obtuvieron a cambio de espacio? ¿Tiempo negativo?
Uso de la clase de diccionario.
Siguiendo con el escenario de cadenas, algunas personas pueden pensar en usar tablas Hash y otras estructuras similares para acelerar. Sí, esta es una buena idea, pero las tablas Hash no siempre son la mejor solución. Hagamos una prueba:
bool estático privado ImplementByHashSet(List
{
HashSet
conjunto de retorno. Contiene (valor);
}
Eche un vistazo a los resultados de la ejecución:
Utilice ImplementByHashSet
5114ms
GC 0:38
CS 1:38
CS 2:38
el resultado es verdadero
Sorprendentemente, la velocidad es más del doble de lenta que con ToUpper, y la basura de segunda generación también se recolecta 38 veces (al ejecutar la recolección de basura de segunda generación, se forzará la recolección de basura de primera generación y de generación cero).
Sin embargo, la idea de utilizar una tabla Hash o similar para acelerar el proceso es una idea muy correcta, pero la premisa es que la tabla Hash en sí se puede almacenar en caché, por ejemplo:
Func estática privada
{
HashSet
conjunto de retorno. Contiene;
}
Luego modifique el método principal para:
Console.WriteLine("Usar ImplementByHashSet2");
resultado = MedirRendimiento(s =>
{
var f = ImplementByHashSet2(cadenas);
bool ret = falso;
para (int i = 0; i < 1000; i++)
{
ret = f(s);
}
volver atrás;
}, "yZh", 1);
Console.WriteLine("el resultado es " + resultado.ToString());
Consola.ReadLine();
Echemos un vistazo a los resultados:
Utilice ImplementByHashSet2
6 ms
CG 0:0
CG 1:0
CG 2:0
el resultado es verdadero
El rendimiento ha aumentado espectacularmente.
Más
¿Qué ralentiza c#/.net? En pocas palabras: creación innecesaria de objetos, sincronización innecesaria, métodos ineficientes de ejecución de bucles (como la reflexión que fue criticada por firelong, pero ms no le permite usar Invoke en el bucle), uso de estructuras de datos y algoritmos ineficientes (ver observe el sorprendente rendimiento de una estructura similar de la tabla Hash en el caso del almacenamiento en caché, y sabrá la diferencia)
El umbral bajo de c#/.net ayuda a atraer a más programadores a c#/.net hasta cierto punto, pero también reduce mucho el nivel de código de todo el programa c#/.net, lo cual es realmente impresionante.
Por último, no olvide que el rendimiento de un sistema no está determinado por la parte del sistema con mejor rendimiento, sino por la parte del sistema con peor rendimiento. Equipada con una memoria de 16 g, un disco duro de 100 t y una tarjeta gráfica de primer nivel, pero sin una CPU 386, el rendimiento de esta computadora es el rendimiento de la 386. De manera similar, no importa cuán bueno sea C#/.net, si las habilidades del programador son deficientes, el rendimiento del programa escrito naturalmente será deficiente.