Skip to content

IO performance improvements

Pre-release
Pre-release
Compare
Choose a tag to compare
@louthy louthy released this 04 Sep 14:06
· 140 commits to main since this release

In one of the proposals leading up to the big v5 refactor, I discussed the idea of using SpinWait as a lightweight waiting technique to avoid the use of the async/await machinery everywhere. I also mentioned that the idea might be too primitive. Well, it was.

So, I have modified the internals of the IO monad (which is where all async code lives now) to have four possible states: IOSync, IOAsync, IOPure, and IOFail. These are just types derived from IO (you never see them).

The idea is that any actual asynchronous IO will just use the regular async/await machinery (internally in IOAsync), any synchronous IO will be free of async/await (in IOSync), and any pure or failure values will have a super simplified implementation that has no laziness at all and just can pre-compute.

The TestBed.Web sample with the TestBed.Web.Runner NBomber test now runs both the sync and async versions with exactly the same performance and with no thread starvation; and without any special need to fork the IO operation on the sync version.

I consider that a big win which will allow users to avoid async/await entirely (if they so wish), one of the goals of 'Drop all Async variants' proposal.

app.MapGet("/sync", 
    () => {
        var effect = liftIO(async () =>
                            {
                                await Task.Delay(1000);
                                return "Hello, World";
                            });

        return effect.Run();
    });

app.MapGet("/async", 
    async () => {
        var effect = liftIO(async () =>
                            {
                                await Task.Delay(1000);
                                return "Hello, World";
                            });
        
        return await effect.RunAsync();
    });

Issue fix