2010-04-07 14 views
13

(fondo: Why should I use int instead of a byte or short in C#)int, el rendimiento a corto byte en la espalda con espalda para-bucles

para satisfacer mi propia curiosidad acerca de los pros y los contras de usar el número entero "tamaño adecuado" frente a la "optimizado" integer escribí el siguiente código que reforzaba lo que antes era cierto sobre el rendimiento int en .Net (y que se explica en el enlace anterior), que es que está optimizado para el rendimiento int en lugar de corto o byte.

DateTime t; 
long a, b, c; 

t = DateTime.Now; 
for (int index = 0; index < 127; index++) 
{ 
    Console.WriteLine(index.ToString()); 
}   
a = DateTime.Now.Ticks - t.Ticks; 

t = DateTime.Now; 
for (short index = 0; index < 127; index++) 
{ 
    Console.WriteLine(index.ToString()); 
} 

b=DateTime.Now.Ticks - t.Ticks; 

t = DateTime.Now;   
for (byte index = 0; index < 127; index++) 
{ 
    Console.WriteLine(index.ToString()); 
} 
c=DateTime.Now.Ticks - t.Ticks; 

Console.WriteLine(a.ToString()); 
Console.WriteLine(b.ToString()); 
Console.WriteLine(c.ToString()); 

Esto da resultados más o menos constantes en la zona de ...

~ 950000

~ 2000000

~ 1700000

que está en línea con lo que esperaría ver

Sin embargo cuando intento de repetir los bucles para cada tipo de datos como este ...

t = DateTime.Now; 
for (int index = 0; index < 127; index++) 
{ 
    Console.WriteLine(index.ToString()); 
} 
for (int index = 0; index < 127; index++) 
{ 
    Console.WriteLine(index.ToString()); 
} 
for (int index = 0; index < 127; index++) 
{ 
    Console.WriteLine(index.ToString()); 
} 
a = DateTime.Now.Ticks - t.Ticks; 

Los números son más como ...

~ 4500000

~ 3100000

~ 300000

Que me parece desconcertante. ¿Alguien puede ofrecer una explicación?

NOTA: Con el fin de comparar igual por igual me he limitado a los bucles 127 debido a la gama de la tipo byte valor. También esto es un acto de curiosidad no micro-optimización del código de producción.

+1

'byte' tiene el rango de 0-255. No es un tipo de datos firmado. –

+6

Además, la clase 'DateTime' no es adecuada para el perfil de rendimiento de bajo nivel. Use 'System.Diagnostics.Stopwatch'. –

+0

@Aaronaught, Jon: Gracias por la solución. Tengo algunas aclaraciones ... índice <255/127; ... En este código, 255/127 es siempre Byte/Corto/Tipo de datos Int. O.Net IL cambiará su tipo de datos de 255/127 para indexar el tipo de datos para respectivos para los bucles? Podemos declarar constante para el tipo de datos respectivo for-loop y check-it? – Thulasiram

Respuesta

35

En primer lugar, no es .NET que se optimiza para int rendimiento, que es la máquinaque se optimiza porque 32 bits es el tamaño de la palabra nativa (a menos que esté en x64, en cuyo caso es long o 64 bits) .

En segundo lugar, está escribiendo en la consola dentro de cada bucle, eso va a ser mucho más caro que incrementar y probar el contador de bucles, por lo que no está midiendo nada realista aquí.

En tercer lugar, un byte tiene un alcance de hasta 255, por lo que puede repetir 254 veces (si intenta hacer 255 se desbordará y el bucle nunca terminará, pero no es necesario detenerse en 128).

En cuarto lugar, no está haciendo ningún lugar cerca de suficientes iteraciones para el perfil. Iterar un ciclo cerrado 128 o incluso 254 veces no tiene sentido. Lo que debe hacer es colocar el bucle byte/short/int dentro de otro bucle que itera un número de veces mucho mayor, digamos 10 millones, y verifique los resultados.

Por último, el uso de DateTime.Now dentro de los cálculos dará lugar a cierto "ruido" de sincronización durante la creación de perfiles. Se recomienda (y más fácil) usar la clase Stopwatch en su lugar.

En la línea inferior, esto necesita muchos cambios antes de que pueda ser una prueba de rendimiento válida.


Aquí es lo que yo considero que es un programa de prueba más precisa:

class Program 
{ 
    const int TestIterations = 5000000; 

    static void Main(string[] args) 
    { 
     RunTest("Byte Loop", TestByteLoop, TestIterations); 
     RunTest("Short Loop", TestShortLoop, TestIterations); 
     RunTest("Int Loop", TestIntLoop, TestIterations); 
     Console.ReadLine(); 
    } 

    static void RunTest(string testName, Action action, int iterations) 
    { 
     Stopwatch sw = new Stopwatch(); 
     sw.Start(); 
     for (int i = 0; i < iterations; i++) 
     { 
      action(); 
     } 
     sw.Stop(); 
     Console.WriteLine("{0}: Elapsed Time = {1}", testName, sw.Elapsed); 
    } 

    static void TestByteLoop() 
    { 
     int x = 0; 
     for (byte b = 0; b < 255; b++) 
      ++x; 
    } 

    static void TestShortLoop() 
    { 
     int x = 0; 
     for (short s = 0; s < 255; s++) 
      ++x; 
    } 

    static void TestIntLoop() 
    { 
     int x = 0; 
     for (int i = 0; i < 255; i++) 
      ++x; 
    } 
} 

Esto va en cada bucle dentro de un bucle mucho más grande (5 millones de iteraciones) y realiza una operación muy simple dentro de la loop (incrementa una variable). Los resultados para mí fueron:

Byte Loop: Tiempo transcurrido = 00: 00: 03,8949910
bucle corto: Tiempo transcurrido = 00: 00: 03,9098782
Int Loop: Tiempo transcurrido = 00: 00: 03,2986990

Por lo tanto, no hay una diferencia apreciable.

Además, asegúrese de que su perfil en modo de lanzamiento, mucha gente olvida y prueba en modo de depuración, que será significativamente menos precisa.

+1

Gracias Ooh, nunca he intentado hacer un perfil de mi código antes. Buenos puntos, tomados a bordo :) – gingerbreadboy

+2

@Aaronaught: Me encanta lo similares que son nuestros puntos de referencia :) –

+2

@Jon: Juro que no copié el tuyo. : P – Aaronaught

11

La mayor parte de este tiempo probablemente se haya pasado escribiendo a la consola. Trate de hacer algo distinto de aquel en el bucle ...

Además:

  • Usando DateTime.Now es una mala manera de medir el tiempo. Use System.Diagnostics.Stopwatch en su lugar
  • Una vez que haya eliminado la llamada Console.WriteLine, un bucle de 127 iteraciones será demasiado corto para medir. Debe ejecutar el ciclo lotes de veces para obtener una medición adecuada.

Aquí es mi punto de referencia:

using System; 
using System.Diagnostics; 

public static class Test 
{  
    const int Iterations = 100000; 

    static void Main(string[] args) 
    { 
     Measure(ByteLoop); 
     Measure(ShortLoop); 
     Measure(IntLoop); 
     Measure(BackToBack); 
     Measure(DelegateOverhead); 
    } 

    static void Measure(Action action) 
    { 
     GC.Collect(); 
     GC.WaitForPendingFinalizers(); 
     GC.Collect(); 
     Stopwatch sw = Stopwatch.StartNew(); 
     for (int i = 0; i < Iterations; i++) 
     { 
      action(); 
     } 
     sw.Stop(); 
     Console.WriteLine("{0}: {1}ms", action.Method.Name, 
          sw.ElapsedMilliseconds); 
    } 

    static void ByteLoop() 
    { 
     for (byte index = 0; index < 127; index++) 
     { 
      index.ToString(); 
     } 
    } 

    static void ShortLoop() 
    { 
     for (short index = 0; index < 127; index++) 
     { 
      index.ToString(); 
     } 
    } 

    static void IntLoop() 
    { 
     for (int index = 0; index < 127; index++) 
     { 
      index.ToString(); 
     } 
    } 

    static void BackToBack() 
    { 
     for (byte index = 0; index < 127; index++) 
     { 
      index.ToString(); 
     } 
     for (short index = 0; index < 127; index++) 
     { 
      index.ToString(); 
     } 
     for (int index = 0; index < 127; index++) 
     { 
      index.ToString(); 
     } 
    } 

    static void DelegateOverhead() 
    { 
     // Nothing. Let's see how much 
     // overhead there is just for calling 
     // this repeatedly... 
    } 
} 

Y los resultados:

ByteLoop: 6585ms 
ShortLoop: 6342ms 
IntLoop: 6404ms 
BackToBack: 19757ms 
DelegateOverhead: 1ms 

(Esto es en un netbook - ajustar el número de iteraciones hasta que llegue algo sensato :)

Eso parece demostrar que, básicamente, no es significativo diferente del tipo que utiliza.

+0

pero todos los loops están escribiendo en la consola el mismo número de veces, es decir, 127 x n-loops – gingerbreadboy

+0

aunque supongo que int.toString() podría tomar más tiempo que byte.toString() tal vez? – gingerbreadboy

+5

@runrunraygun: 'Console.WriteLine' es una operación asíncrona con tiempo de ejecución no confiable. Si bien no es muy probable que tenga un efecto dramático en sus resultados, use algo más confiable. Además, 'int.ToString()' no es la misma función que 'byte.ToString()', por lo que no está realizando la misma acción en cada ciclo. –

0

Generar perfiles El código .Net es muy complicado porque el entorno de tiempo de ejecución en el que se ejecuta el código de byte compilado puede realizar optimizaciones en tiempo de ejecución en el código de bytes.En su segundo ejemplo, el compilador JIT probablemente detectó el código repetido y creó una versión más optimizada. Pero, sin una descripción realmente detallada de cómo funciona el sistema en tiempo de ejecución, es imposible saber qué le sucederá a su código. Y sería una tontería tratar de adivinar en base a la experimentación, ya que Microsoft tiene perfectamente el derecho de rediseñar el motor JIT en cualquier momento, siempre que no rompa ninguna funcionalidad.

+0

Ejecutar el código dentro del depurador (o, más exactamente, compilar y ejecutar bajo la configuración predeterminada para el perfil de depuración con el que se crea un proyecto VS) elimina la posibilidad del tipo de optimización de la que está hablando. –

+0

@ Adam: ¿Pero quién ejecutaría el código en un depurador? Me he dado cuenta de que en VS2005 el código se ejecuta mucho más lento dentro del depurador que por sí solo. IIRC, alguien aquí mencionó que el resultado del compilador .net de depuración y el compilador .net de lanzamiento eran casi idénticos y era el hecho de que el código se ejecutaba de forma independiente en lugar de dentro del depurador que marcaba la diferencia. – Skizz

+0

La desactivación de optimizaciones (que se realiza de forma predeterminada en la configuración de Depuración) es específicamente lo que elimina el tipo de "optimización de distancia" de la que está hablando. Adjuntar * cualquier * depurador puede tener un efecto negativo en el rendimiento, pero ese es un problema diferente. Las salidas del compilador con optimizaciones habilitadas son, de hecho, diferentes de la salida con optimizaciones desactivadas. –

1

Probé los dos programas anteriores porque parecían producir resultados diferentes y posiblemente contradictorios en mi máquina de desarrollo.

Las salidas de instrumento de prueba Aaronaughts'

Short Loop: Elapsed Time = 00:00:00.8299340 
Byte Loop: Elapsed Time = 00:00:00.8398556 
Int Loop: Elapsed Time = 00:00:00.3217386 
Long Loop: Elapsed Time = 00:00:00.7816368 

enteros son mucho más rápido

salidas de Jon

ByteLoop: 1126ms 
ShortLoop: 1115ms 
IntLoop: 1096ms 
BackToBack: 3283ms 
DelegateOverhead: 0ms 

nada en él

Jon tiene la constante fija grande de llamar tirar en los resultados que pueden estar ocultando el posible beneficio s que podría ocurrir si el trabajo realizado en el ciclo fue menor. Aaronaught está utilizando un sistema operativo de 32 bits que parece no beneficiarse del uso tanto de la plataforma x64 que estoy usando.

Hardware/Software resultados se recogieron en un Core i7 975 a 3,33 GHz con Turbo discapacitados y la afinidad conjunto básico para reducir el impacto de otras tareas. La configuración de rendimiento está configurada al máximo y el escáner de virus/tareas de fondo innecesarias suspendidas. Windows 7 x64 ultimate con 11 GB de ram de repuesto y muy poca actividad de IO. Ejecutar en la configuración de lanzamiento integrada en 2008 sin un depurador o generador de perfiles adjuntos.

Repetibilidad Originalmente repetido 10 veces cambiando el orden de ejecución de cada prueba. La variación fue insignificante, así que solo publiqué mi primer resultado. En la carga máxima de la CPU, la relación de tiempos de ejecución se mantuvo constante. Repetición se ejecuta en múltiples cuchillas Xeon XP x64 da más o menos los mismos resultados después de tener en cuenta la generación de CPU y Ghz

de perfiles Redgate/JetBrains/Slimtune/perfilador CLR y mi propio perfilador todo indica que los resultados son correctos.

Depuración compilación El uso de la configuración de depuración en VS da resultados consistentes como los de Aaronaught.

+0

Estoy ejecutando un cuadro x64. Ese es un resultado bastante anómalo para la primera prueba, parece que las versiones 'short' y' byte' tomaron mucho más tiempo de lo que deberían, mientras que la versión 'int' estaba muy cerca de la mía. ¿Hiciste la prueba algunas veces? ¿Tuviste algo más funcionando al mismo tiempo? – Aaronaught

+0

¿Ha intentado volver a ordenar los bucles de byte corto para ver si hay alguna diferencia? En caso de que el compilador JIT esté decidiendo que vale la pena optimizar un tercer ciclo, ya que parece ser una operación común. Solo un pensamiento. Sería interesante de ver – Skizz

+0

@Aaronaught Cambiar mi configuración a x86 dlls igualó mis resultados. Es por eso que supuse que estabas usando un sistema operativo de 32 bits. – Steve

0

La escritura de consola tiene cero que ver con el rendimiento real de los datos. Tiene más que ver con la interacción con las llamadas de la biblioteca de la consola. Te sugiero que hagas algo interesante dentro de esos bucles que es independiente del tamaño de los datos.

Sugerencias: desplazamientos de bit, multiplica, la manipulación de matrices, adición, muchos otros ...

4

Sólo por curiosidad he modificado un litte el programa de Aaronaught y compilado en ambos modos x86 y x64. Extraño, Int funciona mucho más rápido en x64:

x 86

Byte Loop: Tiempo transcurrido = 00: 00: 00,8636454
bucle corto: tiempo transcurrido = 00:00:00.8795518
ushort Loop: Tiempo transcurrido = 00: 00: 00,8630357
Int Loop: Tiempo transcurrido = 00: 00: 00,5184154
UInt Loop: Tiempo transcurrido = 00: 00: 00,4950156
largo Loop: Tiempo transcurrido = 00: 00: 01,2941183
ULONG Loop: Tiempo transcurrido = 00: 00: 01,3023409

x64

Byte Loop: Tiempo transcurrido = 00: 00: 01,0646588
bucle corto: tiempo transcurrido = 00:00: 01.0719330
ushort Loop: Tiempo transcurrido = 00: 00: 01,0711545
Int Loop: Tiempo transcurrido = 00: 00: 00,2462848
UInt Loop: Tiempo transcurrido = 00: 00: 00,4708777
largo Loop: Tiempo transcurrido = 00:00 : 00.5242272
ULong Loop: Tiempo transcurrido = 00: 00: 00.5144035