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