Why is internal .NET method so much faster than simpler code copied from .NET open source?

186 Views Asked by At

I have following code based on this question. Fiddle here. https://dotnetfiddle.net/vchYHj

using System;
using System.Diagnostics;
public class Program
{
    public static void Main()
    {
        ReadOnlySpan<byte> r = stackalloc byte[] { 52, 48, 48, 55, 48 };
        var sw = new Stopwatch();
        int result = 0;


        sw.Restart();
        for (int i=0; i<10000000; i++) result =  Test1(r);
        sw.Stop();
        Console.WriteLine($"Test1 took {sw.ElapsedMilliseconds}ms {result}");


        sw.Restart();
        for (int i=0; i<10000000; i++) result = Test2(r);
        sw.Stop();
        Console.WriteLine($"Test2 took {sw.ElapsedMilliseconds}ms {result}");

    }

    public static int Test1(ReadOnlySpan<byte> source)
    {
        int answer = 0;
        int index = 0;
        int c = source[index];
        answer = c - '0';
        while (true)
        {
            index++;
            if ((uint)index >= (uint)source.Length)
                break;
            c = source[index];
            answer = answer * 10 + c - '0';
        }
         return answer;
    }
    
    public static int Test2(ReadOnlySpan<byte> data)
    {
        if (System.Buffers.Text.Utf8Parser.TryParse(data, out int value, out int bytes))
        {
            return(value);
        }   
        else
            return(0);
    }
}

Test1 took 244ms 40070
Test2 took 99ms 40070

Why is the second method Test1 so much slower on .NET fiddle, when it is copied exactly from .NET open source and is even much simpler?

https://github.com/dotnet/runtime/blob/main/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/Utf8Parser/Utf8Parser.Integer.Unsigned.N.cs#L53

2

There are 2 best solutions below

2
On BEST ANSWER

Why is the second method Test1 so much slower on .NET fiddle,

One reason would be that code compiled by .NET fiddle code is not optimized. As a simple example, consider that the following code

#if DEBUG
    Console.WriteLine("DEBUG");
#else
    Console.WriteLine("RELEASE");
#endif

will print DEBUG when executed with .NET fiddle.

If you want to do benchmarks, I recommend reading Eric Lippert's blog series on that topic first. Benchmarking the Debug build instead of the Release build is just one of the common mistakes listed there.

0
On

You can see the original code here: Utf8Parser.TryParse. This method then calls TryParseInt32D which does the heavy work and is highly optimized.

The code is ugly and unwraps loops and uses goto's. Very interesting and totally okay in a library used by millions of people, but this is not how you should write code.

They certainly made a lot of benchmarks to figure out which optimizations work best. If you want a very fast code, you cannot just assume your code will be fast. It depends on a lot of factors like how your code is translated to IL, how this IL is translated native code, how much 1st level cache your CPU has, etc., etc.