Qual è il sovraccarico dell’istruzione fissa C # su una struttura non sicura gestita contenente array fissi?

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.

In particolare, c’è qualche ragione per evitare il modello mostrato dalla class ‘MultipleFixed’ sotto? Il costo di fissare semplicemente i dati non è zero, vicino allo zero (== costo simile all’impostazione e cancellazione di un singolo flag quando si entra / esce dall’ambito fisso), o è abbastanza significativo da evitare quando ansible?

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 piattaforms che non supportano il codice non sicuro. (Sì, è un codice aggiuntivo, ma qualunque cosa occorra ..)

 

     structe non sicuro ByteArray
     {
        dati a byte fisso pubblici [1024];
     }

     class MultipleFixed
     {
        vuoto non sicuro SetValue (ref ByteArray bytes, int index, byte value)
        {
            fisso (byte * data = byte.Data)
            {
                data [indice] = valore;
            }
        }

         bool non sicuro Validate (ref ByteArray bytes, int index, byte expectedValue)
         {
            fisso (byte * data = byte.Data)
            {
                dati di ritorno [indice] == expectedValue;
            }
         }

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

     class SingleFixed
     {
        vuoto non sicuro SetValue (byte * dati, indice int, valore byte)
        {
            data [indice] = valore;
        }

         bool non valido Convalida (byte * dati, indice int, byte valore atteso)
         {
            dati di ritorno [indice] == expectedValue;
         }

         Test non sicuro non valido (ref byte ByteArray)
         {
             fisso (byte * data = byte.Data)
             {
                 SetValue (dati, 0, 1);
                 Convalida (dati, 0, 1);
             }
         }
     }

Inoltre, ho cercato domande simili e il più vicino che ho trovato è stato questo , ma questa domanda è diversa in quanto riguarda solo il puro codice gestito e i costi specifici dell’utilizzo fisso in quel contesto.

Grazie per qualsiasi informazione!

Empiricamente, l’overhead sembra essere, nel migliore dei casi, ~ 270% su JIT a 32 bit e ~ 200% su 64 bit (e l’overhead peggiora più volte ” fixed “). Quindi proverei a minimizzare fixed blocchi fixed se le prestazioni sono davvero critiche.

Scusa, non conosco abbastanza bene il codice fisso / non sicuro per sapere perché è così


Dettagli

Ho anche aggiunto alcuni metodi TestMore che chiamano i tuoi due metodi di test 10 volte anziché 2 per fornire 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, JIT a 32 bit:

 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, JIT a 64 bit:

 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 

Questa è stata una domanda davvero interessante che ho avuto io stesso.

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

Puoi vedere i test che corro e i risultati sotto, ma ci sono le seguenti osservazioni che traggo da quelli:

  • le prestazioni dell’utilizzo di ‘fixed’ con puntatori puri (x *), senza IntPtr, sono uguali a quelle del codice gestito; in modalità Release, è anche molto meglio se fixed non viene usato troppo spesso – questo è il modo più perforante per accedere a più valori di array
  • in modalità debug, l’uso di “fixed” (all’interno di un loop) ha un impatto negativo sulle prestazioni, ma in modalità di rilascio funziona quasi come il normale accesso agli array (metodo FixedAccess);
  • usando ‘ref’ su un valore di parametro di tipo reference (float []) era costantemente più o ugualmente performante (entrambe le modalità)
  • La modalità di debug ha un calo significativo delle prestazioni rispetto alla modalità di rilascio quando si utilizza l’aritmetica IntPtr (IntPtrAccess) ma per entrambe le modalità la prestazione è peggiore del normale accesso dell’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à). Questo è vero per “float” ma non ha impatto per “int”

Esecuzione dei test più volte, fornisce risultati leggermente diversi ma sostanzialmente coerenti. Probabilmente avrei dovuto eseguire molte serie di test e prendere i tempi medi – ma non avevo tempo per quello 🙂

La class 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 i test stessi:

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

e infine i risultati:

Modalità di 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)