nats-io/nats.net.v2

Consider wrapping throw in call to encourage JITter

Opened this issue ยท 3 comments

mtmk commented

NIT/Future: Consider wrapping throw in call to encourage JITter.

Originally posted by @to11mtm in #303 (comment)

So, to elaborate here... Apologies, priorities abound...

In general we should, when throwing in potentially hot methods, prefer to call a ThrowHelper() instead of throwing.

As a very good example

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int GetInt32(ReadOnlySpan<byte> span)
{
if (!Utf8Parser.TryParse(span, out int value, out var consumed))
{
throw new Exception(); // throw...
}
return value;
}

the fact we are throwing actually causes a number of optimizations to not happen and can also impact inline ability.

An example refactoring would be:

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    private static int GetInt32(ReadOnlySpan<byte> span)
    {
        if (!Utf8Parser.TryParse(span, out int value, out var consumed))
        {
            ThrowOnUtf8ParseFail();
        }

        return value;
    }
    [MethodImpl(MethodImplOptions.NoInlining)]
    private static void ThrowOnUtf8ParseFail()
    {
        throw new Exception();
    }

Further Docs and explanation of potential value for perf: (FWIW these are from CommunityToolkit's Diagnostic analyzer docs, so we could, if we wanted, add as an analyzer to help identify across projects[0])

https://learn.microsoft.com/en-us/dotnet/communitytoolkit/diagnostics/throwhelper#technical-details

We probably should have some form of:

internal static class ThrowHelpers
{
    //example, add others to taste
    [MethodImpl(MethodImplOptions.NoInlining)] //may be too paranoid
    public void ThrowNatsException(string message)
    {
        throw new NatsException(message);
    }
}

And use that when throwing on hot paths/etc.

[0] - I think that analyzer will only really work if we take CommunityToolKit as a dependency and we want to stay lean, so probably not for now.

mtmk commented

Makes sense. Also runtime libs are always using this. We could start with read/write loops maybe?