Usually I don't start using pointers for a few microseconds here and there.
But with this implementation I started getting results of factor 2.
using System;
class BenchmarkPointers {
public static void Main() {
#if ALLOW_UNSAFE
Console.WriteLine("This is safe code");
#else
Console.WriteLine("This is unsafe code");
#endif
int bs = 0x100000;
byte[] buffer = new byte[bs];
Random rnd = new Random(DateTime.Now.GetHashCode());
long total = 0;
long avg = 0;
int max = 100;
DateTime start = DateTime.Now;
for(int i = 0; i <= max; i++) {
start = DateTime.Now;
RandomizeBuffer(ref buffer, bs, rnd);
TimeSpan ts = DateTime.Now - start;
total += (long)ts.TotalMilliseconds;
Console.WriteLine("Pass {0} took {1} ms", i, ts.TotalMilliseconds);
}
avg = total / max;
Console.WriteLine("Avarage time for one pass was: {0}ms\nTotal time over {1} passes was {2}ms", avg, max, total);
}
public static
#if ALLOW_UNSAFE
unsafe
#endif
void RandomizeBuffer(ref byte[] buffer, int bufferSize, Random rnd) {
#if ALLOW_UNSAFE
fixed(byte * pBuffer = buffer)
#endif
for(int i = 0; i < bufferSize; i += 4) {
int k = rnd.Next(int.MinValue, int.MaxValue);
#if ALLOW_UNSAFE
// One of the rare moments when I like to use pointers (with type casting)
*((int*)(pBuffer + i)) = k;
#else
byte[] bits = BitConverter.GetBytes(k);
buffer[i ] = bits[0];
buffer[i + 1] = bits[1];
buffer[i + 2] = bits[2];
buffer[i + 3] = bits[3];
#endif
}
}
}
Now essentially I need to put the 32bit integers I get from Random.Next into a byte array.
I started implementing the randomizer as
unsafe void RandomizeBuffer(byte **pBuffer, int bufferSize, Random rnd)
Thats why I got the size in the signature and thats also why I first used the 'pointery' approach. (Since then I removed one deref in the method body)
Then I remembered that C# has reference parameters so I refactored... but noticed my code took longer to execute.
Hence the little benchmark here.
I'm wondering whether there is a managed/safe way to break up lot's of ints into bytes.
Of course since results can vary from circumstance to circumstance here my latest results:
csc Benchmark.cs
...
Avarage time for one pass was: 31ms
Total time over 100 passes was 3197ms
and
csc -define:ALLOW_UNSAFE -unsafe benchmark.cs
...
Avarage time for one pass was: 19ms
Total time over 100 passes was 1931ms
Also I noticed with the unsafe execution there is quiet a fluctuation in execution times of the passes. i.E.:
Pass 85 took 15.6097 ms
Pass 86 took 15.6251 ms
Pass 87 took 31.2502 ms
Pass 88 took 15.626 ms
Pass 89 took 15.6246 ms
I can only imagine it has something to do with the CPU Pipeline. But I am no expert in CPU Architecture or Compiler Internals
Any thoughts on this are welcome!
(Yes also those that say: Don't waste your time with this kind of micro management)
Edit:
Using StopWatch and 1000 passes the results are similar, though the fluctuations have been eliminated.
safe - Avarage time for one pass was: 24ms Total time over 1000 passes was 24606ms
unsafe - Avarage time for one pass was: 16ms Total time over 1000 passes was 16965ms
DateTime.Now
to benchmark anything. As you're discovering here, it typically has a resolution of about ~15 ms, which isn't very useful for precise timing. That's why you're getting those fluctuations. \$\endgroup\$ – Kyle Jul 14 '16 at 22:06