c# - una - ¿Por qué la adición de variables locales hace que el código.NET sea más lento?
variables locales c# (5)
Creo que esto está relacionado con tu otra pregunta. Cuando cambio su código de la siguiente manera, gana la versión de varias líneas.
Uy, solo en x86. En x64, multilínea es la más lenta y el condicional les gana a ambos fácilmente.
class Program
{
static void Main()
{
ConditionalTest();
SingleLineTest();
MultiLineTest();
ConditionalTest();
SingleLineTest();
MultiLineTest();
ConditionalTest();
SingleLineTest();
MultiLineTest();
}
public static void ConditionalTest()
{
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
int count = 0;
for (uint i = 0; i < 1000000000; ++i) {
if (i % 16 == 0) ++count;
}
stopwatch.Stop();
Console.WriteLine("Conditional test --> Count: {0}, Time: {1}", count, stopwatch.ElapsedMilliseconds);
}
public static void SingleLineTest()
{
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
int count = 0;
for (uint i = 0; i < 1000000000; ++i) {
count += i % 16 == 0 ? 1 : 0;
}
stopwatch.Stop();
Console.WriteLine("Single-line test --> Count: {0}, Time: {1}", count, stopwatch.ElapsedMilliseconds);
}
public static void MultiLineTest()
{
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
int count = 0;
for (uint i = 0; i < 1000000000; ++i) {
var isMultipleOf16 = i % 16 == 0;
count += isMultipleOf16 ? 1 : 0;
}
stopwatch.Stop();
Console.WriteLine("Multi-line test --> Count: {0}, Time: {1}", count, stopwatch.ElapsedMilliseconds);
}
}
¿Por qué comentar las dos primeras líneas de este ciclo y quitar el comentario del tercer resultado en una aceleración del 42%?
int count = 0;
for (uint i = 0; i < 1000000000; ++i) {
var isMultipleOf16 = i % 16 == 0;
count += isMultipleOf16 ? 1 : 0;
//count += i % 16 == 0 ? 1 : 0;
}
Detrás del tiempo hay un código de ensamblaje muy diferente: 13 vs. 7 instrucciones en el ciclo. La plataforma es Windows 7 con .NET 4.0 x64. La optimización de código está habilitada y la aplicación de prueba se ejecutó fuera de VS2010. [ Actualización: Repro proyecto , útil para verificar la configuración del proyecto.]
La eliminación del booleano intermedio es una optimización fundamental, una de las más simples de mi era de 1980 Libro del dragón . ¿Cómo no se aplicó la optimización al generar el CIL o JITing el código de máquina x64?
¿Hay un "Realmente compilador, me gustaría que optimices este código, por favor" cambiar? Si bien simpatizo con el sentimiento de que la optimización prematura es similar al amor por el dinero , pude ver la frustración al tratar de perfilar un algoritmo complejo que tenía problemas como este dispersos a lo largo de sus rutinas. Trabajarías a través de los puntos de acceso, pero no tendrías indicios de una región cálida más amplia que podría mejorarse enormemente modificando a mano lo que normalmente damos por descontado del compilador. Espero que me esté perdiendo algo aquí.
Actualización: las diferencias de velocidad también se producen para x86, pero dependen del orden en que los métodos se compilan justo a tiempo. Ver ¿Por qué el orden JIT afecta el rendimiento?
Código de ensamblaje (según se solicite):
var isMultipleOf16 = i % 16 == 0;
00000037 mov eax,edx
00000039 and eax,0Fh
0000003c xor ecx,ecx
0000003e test eax,eax
00000040 sete cl
count += isMultipleOf16 ? 1 : 0;
00000043 movzx eax,cl
00000046 test eax,eax
00000048 jne 0000000000000050
0000004a xor eax,eax
0000004c jmp 0000000000000055
0000004e xchg ax,ax
00000050 mov eax,1
00000055 lea r8d,[rbx+rax]
count += i % 16 == 0 ? 1 : 0;
00000037 mov eax,ecx
00000039 and eax,0Fh
0000003c je 0000000000000042
0000003e xor eax,eax
00000040 jmp 0000000000000047
00000042 mov eax,1
00000047 lea edx,[rbx+rax]
Es un error en .NET Framework.
Bueno, realmente solo estoy especulando, pero envié un informe de error sobre Microsoft Connect para ver lo que dicen. Después de que Microsoft eliminó ese informe, volví a roslyn proyecto roslyn en GitHub.
Actualización: Microsoft ha trasladado el problema al proyecto coreclr . De los comentarios sobre el tema, llamarlo un error parece un poco fuerte; es más una optimización faltante.
La pregunta debería ser "¿Por qué veo tanta diferencia en mi máquina?". No puedo reproducir una diferencia de velocidad tan grande y sospecho que hay algo específico en su entorno. Sin embargo, es muy difícil decir lo que puede ser. Puede haber algunas opciones (compilador) que haya establecido hace algún tiempo y las haya olvidado.
He creado una aplicación de consola, reconstruido en modo Release (x86) y ejecutándolo fuera de VS. Los resultados son prácticamente idénticos, 1,77 segundos para ambos métodos. Aquí está el código exacto:
static void Main(string[] args)
{
Stopwatch sw = new Stopwatch();
sw.Start();
int count = 0;
for (uint i = 0; i < 1000000000; ++i)
{
// 1st method
var isMultipleOf16 = i % 16 == 0;
count += isMultipleOf16 ? 1 : 0;
// 2nd method
//count += i % 16 == 0 ? 1 : 0;
}
sw.Stop();
Console.WriteLine(string.Format("Ellapsed {0}, count {1}", sw.Elapsed, count));
Console.ReadKey();
}
Por favor, cualquiera que tenga 5 minutos copie el código, reconstruya, corra fuera de VS y publique los resultados en comentarios a esta respuesta. Me gustaría evitar decir "funciona en mi máquina".
EDITAR
Para estar seguro de haber creado una aplicación Winforms de 64 bits y los resultados son similares a los de la pregunta, el primer método es más lento (1.57 segundos) que el segundo (1.05 segundos). La diferencia que observo es del 33%, todavía mucho. Parece que hay un error en el compilador .NET4 64 bit JIT.
No puedo hablar con el compilador .NET, sus optimizaciones o incluso CUANDO realiza sus optimizaciones.
Pero en este caso específico, si el compilador plegaba esa variable booleana en la instrucción real, y usted debía intentar depurar este código, el código optimizado no coincidiría con el código tal como estaba escrito. No podrá pasar de una sola vez por encima de la asignación isMulitpleOf16 y verificar su valor.
Eso es solo un ejemplo de donde la optimización puede ser desactivada. Podría haber otros. La optimización puede ocurrir durante la fase de carga del código, en lugar de la fase de generación de código del CLR.
Los tiempos de ejecución modernos son bastante complicados, especialmente si arroja JIT y optimización dinámica durante el tiempo de ejecución. Me siento agradecido de que el código haga lo que dice a veces.
Tiendo a pensarlo de esta manera: las personas que trabajan en el compilador solo pueden hacer muchas cosas al año. Si en ese momento pudieran implementar lambdas o muchas optimizaciones clásicas, votaría por lambdas. C # es un lenguaje que es eficiente en términos de lectura de código y esfuerzo de escritura, más que en términos de tiempo de ejecución.
Por lo tanto, es razonable que el equipo se concentre en características que maximizan la eficiencia de lectura / escritura, en lugar de la eficiencia de ejecución en un caso de esquina determinado (de los cuales probablemente haya miles).
Inicialmente, creo, la idea era que JITter haría toda la optimización. Desafortunadamente, el JITting toma cantidades de tiempo considerables y cualquier optimización avanzada empeorará las cosas. Entonces eso no funcionó tan bien como uno podría haber esperado.
Una cosa que encontré sobre la programación de código muy rápido en C # es que con bastante frecuencia se golpea un cuello de botella severo antes de que cualquier optimización como usted mencione pueda hacer la diferencia. Me gusta si asigna millones de objetos. C # te deja muy poco en términos de evitar el costo: puedes usar matrices de estructuras en su lugar, pero el código resultante es realmente feo en comparación. Mi punto es que muchas otras decisiones sobre C # y .NET hacen que esas optimizaciones específicas valen menos la pena de lo que serían en algo así como un compilador de C ++. Diablos, incluso abandonaron las optimizaciones específicas de la CPU en NGEN , negociando el rendimiento para la eficiencia del programador (depurador).
Habiendo dicho todo esto, me encantaría C # que realmente hizo uso de las optimizaciones que C ++ usó desde la década de 1990. Simplemente no a expensas de funciones como, por ejemplo, async / await.