bytecodealliance / wasmtime-dotnet

.NET embedding of Wasmtime https://bytecodealliance.github.io/wasmtime-dotnet/
Apache License 2.0
409 stars 52 forks source link

Better temporary memory #229

Closed martindevans closed 1 year ago

martindevans commented 1 year ago

Builds on #228, review/merge that first! (That has been merged now).

While working on #228 I came up with this idea for replacing the ad-hoc approaches to string->UTF8 conversions that we have around the place at the moment.

Currently we're either doing this simple approach that always allocates:

Encoding.UTF8.GetBytes(a_string); // Always allocates

Or this slightly more complex approach that sometimes allocates:

var nameLength = Encoding.UTF8.GetByteCount(name);
var nameBytes = nameLength <= StackallocThreshold ? stackalloc byte[nameLength] : new byte[nameLength]; // Sometimes allocates
Encoding.UTF8.GetBytes(name, nameBytes);

Or even this (😱) approach which never allocates:

byte[]? nameBytesBuffer = null;
var nameLength = Encoding.UTF8.GetByteCount(name);
Span<byte> nameBytes = nameLength <= StackallocThreshold ? stackalloc byte[nameLength] : (nameBytesBuffer = ArrayPool<byte>.Shared.Rent(nameLength)).AsSpan()[..nameLength];
Encoding.UTF8.GetBytes(name, nameBytes);

try
{
    // code goes here
}
finally
{
    if (nameBytesBuffer is not null)
    {
        ArrayPool<byte>.Shared.Return(nameBytesBuffer);
    }
}

Ideally we'd always use the last approach, but no one is proposing that (outside of generated code) because it's just too horrible to look at!


This PR introduces a new system:

using var nameBytes = name.ToUTF8(stackalloc byte[128]);

This will use the provided stackalloc byte span if possible and will fall back to renting from the ArrayPool<byte> if not. When the struct is disposed it returns the rented array to the pool.

The only disadvantage of this approach is it always allocates on the stack, wheras the techniques above only allocate on the stack if necessary. I don't think this is a problem since the worst case is the same.