I was doing some research on the StringReader class in .NET and C#, using the implementation found here: https://referencesource.microsoft.com/#mscorlib/system/io/stringreader.cs
I made a small class which I thought used the same basic implementation for reading a string, but to my surprise my code is over twice as slow as the .NET StringReader.
Here is my class:
public class DataReader
{
private String source;
private int pos;
private int length;
public DataReader(string data)
{
source = data;
length = source.Length;
}
public int Peek()
{
if (pos == length) return -1;
return source[pos];
}
public int Read()
{
if (pos == length) return -1;
return source[pos++];
}
}
Here is my test code:
using System;
using System.IO;
using System.Diagnostics;
using System.Text;
using System.Collections.Generic;
public class Program
{
public static void Main()
{
var s = new String('x', 10000000);
StringReaderTest(s);
DataReaderTest(s);
}
private static void StringReaderTest(string s)
{
var stopwatch = new Stopwatch();
stopwatch.Start();
var reader = new StringReader(s);
while (reader.Peek() > -1)
{
reader.Read();
}
stopwatch.Stop();
Console.WriteLine(stopwatch.ElapsedMilliseconds);
}
private static void DataReaderTest(string s)
{
var stopwatch = new Stopwatch();
stopwatch.Start();
var reader = new DataReader(s);
while (reader.Peek() > -1)
{
reader.Read();
}
stopwatch.Stop();
Console.WriteLine(stopwatch.ElapsedMilliseconds);
}
}
And here is a .NET fiddle of the whole thing. https://dotnetfiddle.net/MqbU5q
This is the output from the Fiddle. My implementation is twice as slow.
77
159
I must have missed something, can anybody please explain?
So firstly,
Stopwatchis not a legitimate benchmarking tool, there are many reasons why it's not appropriate.You should be using BenchmarkDotNet or similar, which pre-warms, pre-JIT's, garbage collects before each run, runs the test multiple times, and alerts you when you are in debug etc.
Here is an example of how you might produce a more reliable benchmark.
Disclaimer: There is more that should be reasoned about in a good benchmark, like using a more realistic representation of your actual data and use cases.
Test Code
Benchmarks
Environment
Results
Summary
As you can see (and as you would expect) your implementation is faster, there are less checks, and it doesn't have to go through layers of inheritance to achieve the same results (ergo the compiler isn't emitting
CallVirtto lookup vtables at runtime)The callvirt instruction calls a late-bound method on an object. That is, the method is chosen based on the runtime type of obj rather than the compile-time class visible in the method pointer.