2011-12-13 13 views
12

He estado tratando de determinar cuál es el costo real de usar la instrucción fija dentro de C# para las estructuras inseguras administradas que contienen arreglos fijos. Tenga en cuenta que no me refiero a las estructuras no administradas.¿Cuál es la sobrecarga de la sentencia fija C# en una estructura administrada insegura que contiene arreglos fijos?

Específicamente, ¿hay alguna razón para evitar el patrón mostrado por la clase 'MultipleFixed' a continuación? ¿El costo de simplemente arreglar los datos es distinto de cero, cerca de cero (== costo similar a la configuración & despejando un solo indicador al entrar/salir del alcance fijo), o es lo suficientemente importante como para evitarlo cuando sea posible?

Obviamente, estas clases están diseñadas para ayudar a explicar la pregunta. Esto es para una estructura de datos de alto uso en un juego XNA donde el rendimiento de lectura/escritura de estos datos es crítico, así que si necesito arreglar el arreglo y pasarlo a todas partes lo haré, pero si no hay ninguna diferencia en absoluto Preferiría mantener el local fijo() en los métodos para ayudar a mantener las firmas de funciones ligeramente más portátiles a las plataformas que no admiten código inseguro. (Sí, es un poco de código extra ronco, pero lo que sea necesario ..)

 

    unsafe struct ByteArray 
    { 
     public fixed byte Data[1024]; 
    } 

    class MultipleFixed 
    { 
     unsafe void SetValue(ref ByteArray bytes, int index, byte value) 
     { 
      fixed(byte* data = bytes.Data) 
      { 
       data[index] = value; 
      } 
     } 

     unsafe bool Validate(ref ByteArray bytes, int index, byte expectedValue) 
     { 
      fixed(byte* data = bytes.Data) 
      { 
       return data[index] == expectedValue; 
      } 
     } 

     void Test(ref ByteArray bytes) 
     { 
      SetValue(ref bytes, 0, 1); 
      Validate(ref bytes, 0, 1); 
     } 
    } 

    class SingleFixed 
    { 
     unsafe void SetValue(byte* data, int index, byte value) 
     { 
      data[index] = value; 
     } 

     unsafe bool Validate(byte* data, int index, byte expectedValue) 
     { 
      return data[index] == expectedValue; 
     } 

     unsafe void Test(ref ByteArray bytes) 
     { 
      fixed(byte* data = bytes.Data) 
      { 
       SetValue(data, 0, 1); 
       Validate(data, 0, 1); 
      } 
     } 
    } 

Además, me buscó preguntas similares y el más cercano que encontré fue this, pero esta cuestión es diferente, ya que se refiere únicamente a pura código administrado y los costos específicos de uso fijo en ese contexto.

¡Gracias por cualquier información!

Respuesta

8

Empíricamente, la sobrecarga parece ser, en el mejor de los casos, ~ 270% en 32 bit JIT y ~ 200% en 64 bit (y la sobrecarga empeora cuantas más veces "llame" fixed). Así que trataría de minimizar sus bloques fixed si el rendimiento es realmente crítico.

Lo sentimos, no soy lo suficientemente familiarizado con código fijo/inseguro para saber por qué ese es el caso


detalles

También añadió algunas TestMore métodos que llaman a sus dos métodos de prueba 10 veces en lugar de 2 para dar un escenario más real del mundo de múltiples métodos llamados en su estructura fixed.

El código utilicé:

class Program 
{ 
    static void Main(string[] args) 
    { 
     var someData = new ByteArray(); 
     int iterations = 1000000000; 
     var multiple = new MultipleFixed(); 
     var single = new SingleFixed(); 

     // Warmup. 
     for (int i = 0; i < 100; i++) 
     { 
      multiple.Test(ref someData); 
      single.Test(ref someData); 
      multiple.TestMore(ref someData); 
      single.TestMore(ref someData); 
     } 

     // Environment. 
     if (Debugger.IsAttached) 
      Console.WriteLine("Debugger is attached!!!!!!!!!! This run is invalid!"); 
     Console.WriteLine("CLR Version: " + Environment.Version); 
     Console.WriteLine("Pointer size: {0} bytes", IntPtr.Size); 
     Console.WriteLine("Iterations: " + iterations); 

     Console.Write("Starting run for Single... "); 
     var sw = Stopwatch.StartNew(); 
     for (int i = 0; i < iterations; i++) 
     { 
      single.Test(ref someData); 
     } 
     sw.Stop(); 
     Console.WriteLine("Completed in {0:N3}ms - {1:N2}/sec", sw.Elapsed.TotalMilliseconds, iterations/sw.Elapsed.TotalSeconds); 

     Console.Write("Starting run for More Single... "); 
     sw = Stopwatch.StartNew(); 
     for (int i = 0; i < iterations; i++) 
     { 
      single.Test(ref someData); 
     } 
     sw.Stop(); 
     Console.WriteLine("Completed in {0:N3}ms - {1:N2}/sec", sw.Elapsed.TotalMilliseconds, iterations/sw.Elapsed.TotalSeconds); 


     Console.Write("Starting run for Multiple... "); 
     sw = Stopwatch.StartNew(); 
     for (int i = 0; i < iterations; i++) 
     { 
      multiple.Test(ref someData); 
     } 
     sw.Stop(); 
     Console.WriteLine("Completed in {0:N3}ms - {1:N2}/sec", sw.Elapsed.TotalMilliseconds, iterations/sw.Elapsed.TotalSeconds); 

     Console.Write("Starting run for More Multiple... "); 
     sw = Stopwatch.StartNew(); 
     for (int i = 0; i < iterations; i++) 
     { 
      multiple.TestMore(ref someData); 
     } 
     sw.Stop(); 
     Console.WriteLine("Completed in {0:N3}ms - {1:N2}/sec", sw.Elapsed.TotalMilliseconds, iterations/sw.Elapsed.TotalSeconds); 


     Console.ReadLine(); 
    } 
} 

unsafe struct ByteArray 
{ 
    public fixed byte Data[1024]; 
} 

class MultipleFixed 
{ 
    unsafe void SetValue(ref ByteArray bytes, int index, byte value) 
    { 
     fixed (byte* data = bytes.Data) 
     { 
      data[index] = value; 
     } 
    } 

    unsafe bool Validate(ref ByteArray bytes, int index, byte expectedValue) 
    { 
     fixed (byte* data = bytes.Data) 
     { 
      return data[index] == expectedValue; 
     } 
    } 

    public void Test(ref ByteArray bytes) 
    { 
     SetValue(ref bytes, 0, 1); 
     Validate(ref bytes, 0, 1); 
    } 
    public void TestMore(ref ByteArray bytes) 
    { 
     SetValue(ref bytes, 0, 1); 
     Validate(ref bytes, 0, 1); 
     SetValue(ref bytes, 0, 2); 
     Validate(ref bytes, 0, 2); 
     SetValue(ref bytes, 0, 3); 
     Validate(ref bytes, 0, 3); 
     SetValue(ref bytes, 0, 4); 
     Validate(ref bytes, 0, 4); 
     SetValue(ref bytes, 0, 5); 
     Validate(ref bytes, 0, 5); 
    } 
} 

class SingleFixed 
{ 
    unsafe void SetValue(byte* data, int index, byte value) 
    { 
     data[index] = value; 
    } 

    unsafe bool Validate(byte* data, int index, byte expectedValue) 
    { 
     return data[index] == expectedValue; 
    } 

    public unsafe void Test(ref ByteArray bytes) 
    { 
     fixed (byte* data = bytes.Data) 
     { 
      SetValue(data, 0, 1); 
      Validate(data, 0, 1); 
     } 
    } 
    public unsafe void TestMore(ref ByteArray bytes) 
    { 
     fixed (byte* data = bytes.Data) 
     { 
      SetValue(data, 0, 1); 
      Validate(data, 0, 1); 
      SetValue(data, 0, 2); 
      Validate(data, 0, 2); 
      SetValue(data, 0, 3); 
      Validate(data, 0, 3); 
      SetValue(data, 0, 4); 
      Validate(data, 0, 4); 
      SetValue(data, 0, 5); 
      Validate(data, 0, 5); 
     } 
    } 
} 

Y los resultados en .NET 4.0, 32 bits JIT:

CLR Version: 4.0.30319.239 
Pointer size: 4 bytes 
Iterations: 1000000000 
Starting run for Single... Completed in 2,092.350ms - 477,931,580.94/sec 
Starting run for More Single... Completed in 2,236.767ms - 447,073,934.63/sec 
Starting run for Multiple... Completed in 5,775.922ms - 173,132,528.92/sec 
Starting run for More Multiple... Completed in 26,637.862ms - 37,540,550.36/sec 

Y en .NET 4.0, 64 bits JIT:

CLR Version: 4.0.30319.239 
Pointer size: 8 bytes 
Iterations: 1000000000 
Starting run for Single... Completed in 2,907.946ms - 343,885,316.72/sec 
Starting run for More Single... Completed in 2,904.903ms - 344,245,585.63/sec 
Starting run for Multiple... Completed in 5,754.893ms - 173,765,185.93/sec 
Starting run for More Multiple... Completed in 18,679.593ms - 53,534,358.13/sec 
+0

Gracias - buena información! Todavía me pregunto cuál es la razón subyacente de los gastos generales, pero obtener un buen rendimiento es el objetivo principal. –

+0

Sí, voy a ceder ante Skeet, Lippart o Gravel por el "por qué". Pero si juegas con el tamaño de tu estructura, podría decirte si el tiempo de ejecución está haciendo una copia de la estructura '' corregida ". Creo que la operación de fijación copia toda la estructura. (Consulte también: http://www.dotnetperls.com/fixed-buffer) – ligos

+9

Esta prueba no es precisa. Está utilizando fijo de una manera totalmente irrazonable. El uso correcto sería arreglar una vez, escribir muchas veces, deshacer. – JakeSays

9

Esa fue en realidad una pregunta interesante que tuve yo mismo.

Los resultados que logré obtener sugieren razones ligeramente diferentes para la pérdida de rendimiento que la declaración "fija".

Se puede ver las pruebas que corren y los resultados de abajo, pero hay siguientes observaciones que saco de los que:

  • el rendimiento de la utilización de 'fijo' con punteros puros (x *), sin IntPtr, es tan bueno como en código administrado; en modo Release, es incluso el modo mejor si no se utiliza con frecuencia fija - esa es la forma más efectiva de acceder a múltiples valores
  • en modo de depuración, usando 'fijo' (dentro de un ciclo) tiene un gran impacto negativo pero en el modo de liberación, funciona casi tan bien como el acceso normal a la matriz (método FixedAccess);
  • usando 'ref' en un valor de parámetro de tipo de referencia (flotar []) fue consistentemente más o igualmente performant (ambos modos)
  • modo de depuración tiene caída de rendimiento significativo vs Modo de salida cuando se utiliza IntPtr aritmética (IntPtrAccess) pero para Ambos modos el rendimiento fue peor que el acceso normal al arreglo
  • si se usa el desplazamiento no alineado con el desplazamiento de los valores de la matriz, el rendimiento es terrible, independientemente del modo (en realidad lleva el mismo tiempo para ambos modos). Eso es válido para 'float' pero no tiene impacto para 'int'

Ejecutar las pruebas varias veces, ofrece resultados levemente diferentes pero ampliamente consistentes. Probablemente debería haber corrió muchas series de pruebas y tomar los tiempos medios - pero no tenía tiempo para eso :)

La primera clase de prueba:

class Test { 
    public static void NormalAccess (float[] array, int index) { 
     array[index] = array[index] + 2; 
    } 

    public static void NormalRefAccess (ref float[] array, int index) { 
     array[index] = array[index] + 2; 
    } 

    public static void IntPtrAccess (IntPtr arrayPtr, int index) { 
     unsafe { 
      var array = (float*) IntPtr.Add (arrayPtr, index << 2); 
      (*array) = (*array) + 2; 
     } 
    } 

    public static void IntPtrMisalignedAccess (IntPtr arrayPtr, int index) { 
     unsafe { 
      var array = (float*) IntPtr.Add (arrayPtr, index); // getting bits of a float 
      (*array) = (*array) + 2; 
     } 
    } 

    public static void FixedAccess (float[] array, int index) { 
     unsafe { 
      fixed (float* ptr = &array[index]) 
       (*ptr) = (*ptr) + 2; 
     } 
    } 

    public unsafe static void PtrAccess (float* ptr) { 
     (*ptr) = (*ptr) + 2; 
    } 

} 

y las propias pruebas:

static int runs = 1000*1000*100; 
    public static void Print (string name, Stopwatch sw) { 
     Console.WriteLine ("{0}, items/sec = {1:N} \t {2}", sw.Elapsed, (runs/sw.ElapsedMilliseconds) * 1000, name); 
    } 

    static void Main (string[] args) { 
     var buffer = new float[1024*1024*100]; 
     var len = buffer.Length; 

     var sw = new Stopwatch(); 
     for (int i = 0; i < 1000; i++) { 
      Test.FixedAccess (buffer, 55); 
      Test.NormalAccess (buffer, 66); 
     } 

     Console.WriteLine ("Starting {0:N0} items", runs); 


     sw.Restart(); 
     for (int i = 0; i < runs; i++) 
      Test.NormalAccess (buffer, i % len); 
     sw.Stop(); 

     Print ("Normal access", sw); 

     sw.Restart(); 
     for (int i = 0; i < runs; i++) 
      Test.NormalRefAccess (ref buffer, i % len); 
     sw.Stop(); 

     Print ("Normal Ref access", sw); 

     sw.Restart(); 
     unsafe { 
      fixed (float* ptr = &buffer[0]) 
       for (int i = 0; i < runs; i++) { 
        Test.IntPtrAccess ((IntPtr) ptr, i % len); 
       } 
     } 
     sw.Stop(); 

     Print ("IntPtr access (fixed outside loop)", sw); 

     sw.Restart(); 
     unsafe { 
      fixed (float* ptr = &buffer[0]) 
       for (int i = 0; i < runs; i++) { 
        Test.IntPtrMisalignedAccess ((IntPtr) ptr, i % len); 
       } 
     } 
     sw.Stop(); 

     Print ("IntPtr Misaligned access (fixed outside loop)", sw); 

     sw.Restart(); 
     for (int i = 0; i < runs; i++) 
      Test.FixedAccess (buffer, i % len); 
     sw.Stop(); 

     Print ("Fixed access (fixed inside loop)", sw); 

     sw.Restart(); 
     unsafe { 
      fixed (float* ptr = &buffer[0]) { 
       for (int i = 0; i < runs; i++) { 
        Test.PtrAccess (ptr + (i % len)); 
       } 
      } 
     } 
     sw.Stop(); 

     Print ("float* access (fixed outside loop)", sw); 

     sw.Restart(); 
     unsafe { 
      for (int i = 0; i < runs; i++) { 
       fixed (float* ptr = &buffer[i % len]) { 
        Test.PtrAccess (ptr); 
       } 
      } 
     } 
     sw.Stop(); 

     Print ("float* access (fixed in loop)", sw); 

y finalmente los resultados:

modo de depuración

Starting 100,000,000 items 
00:00:01.0373583, items/sec = 96,432,000.00  Normal access 
00:00:00.8582307, items/sec = 116,550,000.00  Normal Ref access 
00:00:01.8822085, items/sec = 53,134,000.00  IntPtr access (fixed outside loop) 
00:00:10.5356369, items/sec = 9,492,000.00  IntPtr Misaligned access (fixed outside loop) 
00:00:01.6860701, items/sec = 59,311,000.00  Fixed access (fixed inside loop) 
00:00:00.7577868, items/sec = 132,100,000.00  float* access (fixed outside loop) 
00:00:01.0387792, items/sec = 96,339,000.00  float* access (fixed in loop) 

modo de lanzamiento

Starting 100,000,000 items 
00:00:00.7454832, items/sec = 134,228,000.00  Normal access 
00:00:00.6619090, items/sec = 151,285,000.00  Normal Ref access 
00:00:00.9859089, items/sec = 101,522,000.00  IntPtr access (fixed outside loop) 
00:00:10.1289018, items/sec = 9,873,000.00  IntPtr Misaligned access (fixed outside loop) 
00:00:00.7899355, items/sec = 126,742,000.00  Fixed access (fixed inside loop) 
00:00:00.5718507, items/sec = 175,131,000.00  float* access (fixed outside loop) 
00:00:00.6842333, items/sec = 146,198,000.00  float* access (fixed in loop) 
Cuestiones relacionadas