2011-12-13 11 views
12

Ho cercato di determinare quale sia il vero costo dell'utilizzo dell'istruzione fissa in C# per le strutture non sicure gestite che contengono array fissi. Si prega di notare che non mi riferisco a strutture non gestite.Qual è il sovraccarico dell'istruzione fissa C# su una struttura non sicura gestita contenente array fissi?

In particolare, c'è qualche motivo per evitare il modello mostrato dalla classe "MultipleFixed" di seguito? Il costo di fissare semplicemente i dati non è zero, vicino allo zero (== costo simile all'impostazione di & che cancella un singolo flag quando si entra/esce dall'ambito fisso), o è abbastanza significativo da evitare quando possibile?

Ovviamente queste classi sono ideate per aiutare a spiegare la domanda. Questo è per una struttura di dati ad alto utilizzo in un gioco XNA in cui le prestazioni di lettura/scrittura di questi dati sono fondamentali, quindi se devo correggere l'array e passarlo ovunque lo farò, ma se non c'è alcuna differenza preferisco mantenere il fisso() locale ai metodi per aiutare a mantenere le firme della funzione leggermente più portabili su piattaforme che non supportano il codice non sicuro. (Sì, il suo codice in più grugnito, ma tutto ciò che serve ..)

 

    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); 
      } 
     } 
    } 

Inoltre, ho cercato domande simili e la più vicina che ho trovato era this, ma questa domanda è differente in quanto riguarda solo puro codice gestito e costi specifici dell'utilizzo fissi in quel contesto.

Grazie per qualsiasi informazione!

risposta

8

Empiricamente, il sovraccarico sembra essere, nel migliore dei casi, ~ 270% su JIT a 32 bit e ~ 200% su 64 bit (e l'overhead peggiora più volte si "chiama" fixed). Quindi proverei a ridurre al minimo i tuoi blocchi fixed se le prestazioni sono davvero critiche.

dispiace, io non sono abbastanza familiarità con il codice fisso/non sicuro di sapere il motivo per cui questo è il caso


dettagli

Ho anche aggiunto alcune TestMore metodi che richiedono i vostri due metodi di prova 10 volte invece di 2 per offrire uno scenario più realistico di più metodi chiamati sulla tua struttura fixed.

Il codice che ho usato:

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); 
     } 
    } 
} 

E i risultati in .NET 4.0, 32 bit 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 

E in .NET 4.0, 64 bit 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

Grazie - buone informazioni! Mi chiedo ancora quale sia la ragione di fondo per il sovraccarico, ma ottenere le buone prestazioni è l'obiettivo principale. –

+0

Sì, mi rimetto a Skeet, Lippart o Gravel per il "perché". Ma se giochi con le dimensioni della tua struct, potrebbe dirti se il runtime sta facendo una copia della struct ogni 'fixed'. La mia ipotesi è che l'operazione di pinning copi l'intera struttura. (Vedi anche: http://www.dotnetperls.com/fixed-buffer) – ligos

+9

Questo test non è accurato. Stai usando fisso in un modo totalmente irragionevole. L'uso corretto sarebbe quello di risolvere una volta, scrivere più volte, unfix. – JakeSays

9

Questa era una domanda davvero interessante che avevo io stesso.

I risultati che sono riuscito a ottenere suggeriscono motivi leggermente diversi per la perdita di prestazioni rispetto alla stessa affermazione "fissa".

si può vedere il test ho eseguito e i risultati qui sotto, ma ci sono osservazioni che seguono ne traggo da quelli:

  • le prestazioni del usando 'fisso' con i puntatori puri (x *), senza IntPtr, è buono come nel codice gestito; in modalità Release, è anche la modalità migliore se fissa non viene usata troppo spesso - questo è il modo più perforante per accedere a più valori di array
  • in modalità Debug, l'utilizzo di "fixed" (all'interno di un loop) ha un impatto negativo sulle prestazioni ma in modalità Release, funziona quasi come il normale accesso agli array (metodo FixedAccess);
  • usando 'ref' su un valore di parametro di riferimento di tipo (float []) era consistentemente più o altrettanto performanti (entrambe le modalità)
  • Modalità debug trovi calo significativo delle prestazioni vs modalità di uscita quando si utilizza IntPtr aritmetica (IntPtrAccess) ma per entrambe le modalità le prestazioni erano peggiori del normale accesso all'array
  • se si utilizza l'offset non allineato all'offset dei valori dell'array, le prestazioni sono terribili, indipendentemente dalla modalità (in realtà richiede lo stesso tempo per entrambe le modalità). Ciò vale per "float" ma non ha alcun impatto per "int"

L'esecuzione dei test più volte, fornisce risultati leggermente diversi ma sostanzialmente coerenti. Probabilmente avrei dovuto corse molte serie di test e prendere i tempi medi - ma non aveva tempo per questo :)

La classe di test prima:

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; 
    } 

} 

E le prove stesse:

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); 

ed infine i risultati:

modalità Debug

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) 

modalità di rilascio

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) 
Problemi correlati