I\'ve been trying to determine what the true cost of using the fixed statement within C# for managed unsafe structs that contain fixed arrays. Please note I am not referring
That was actually interesting question that I had myself.
The results I managed to obtain suggest slightly different reasons for performance loss than 'fixed' statement itself.
You can see the tests I run and the results below but there are following observations I draw from those:
Running the tests multiple times, gives slightly different but broadly consistent results. Probably I should have ran many series of tests and take the average times - but had no time for that :)
The test class first:
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;
}
}
And the tests themselves:
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);
and finally the results:
Debug mode
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)
Release mode
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)