Skip to content

Instantly share code, notes, and snippets.

@neon-sunset
Last active February 13, 2023 19:26
Show Gist options
  • Select an option

  • Save neon-sunset/fbff70d340bdaa8551ae703a185e4d95 to your computer and use it in GitHub Desktop.

Select an option

Save neon-sunset/fbff70d340bdaa8551ae703a185e4d95 to your computer and use it in GitHub Desktop.
A small helper to wrap a common pattern of stackalloc with fallback to array pool into a single data structure.
// This code is licensed under MIT license
// (c) 2022-2023 neon-sunset
using System.Buffers;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
[StructLayout(LayoutKind.Auto)]
public ref struct Buffer<T>
{
private T[]? _rented;
public Span<T> Span { get; }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Buffer(Span<T> initialBuffer, int length)
{
if (length <= 0)
{
LengthOutOfRange();
}
else if (length <= initialBuffer.Length)
{
_rented = null;
Span = initialBuffer[..length];
}
else
{
_rented = ArrayPool<T>.Shared.Rent(length);
Span = _rented.AsSpan(..length);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal Buffer(T[]? rented, int length)
{
_rented = rented;
Span = rented.AsSpan(..length);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Buffer(int length)
{
if (length <= 0)
{
LengthOutOfRange();
}
_rented = ArrayPool<T>.Shared.Rent(length);
Span = _rented.AsSpan(..length);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator Span<T>(Buffer<T> buffer) => buffer.Span;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator ReadOnlySpan<T>(Buffer<T> buffer) => buffer.Span;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Dispose()
{
if (_rented != null)
{
(T[] pooled, _rented) = (_rented, null);
ReturnPooled(pooled);
}
}
// To help JIT in case this is a cold path to not bloat codegen in the finally block
private static void ReturnPooled(T[] pooled) => ArrayPool<T>.Shared.Return(pooled);
[DoesNotReturn]
private static void LengthOutOfRange()
{
throw new ArgumentOutOfRangeException("length");
}
}
public static class Buffer
{
public static Buffer<T> Create<T>(Span<T> initialBuffer, int length) => new(initialBuffer, length);
public static Buffer<T> Create<T>(int length) => new(length);
public static Buffer<byte> Encode(ReadOnlySpan<char> source, Encoding encoding, Span<byte> initialBuffer = default)
{
byte[]? toReturn = null;
var maxLength = encoding.GetMaxByteCount(source.Length);
var destination = initialBuffer.Length >= maxLength
? initialBuffer
: (toReturn = ArrayPool<byte>.Shared.Rent(maxLength));
var bytesWritten = encoding.GetBytes(source, destination);
return toReturn is null
? new Buffer<byte>(destination, bytesWritten)
: new Buffer<byte>(toReturn, bytesWritten);
}
public static Buffer<char> Decode(ReadOnlySpan<byte> source, Encoding encoding, Span<char> initialBuffer = default)
{
char[]? toReturn = null;
var maxLength = encoding.GetMaxCharCount(source.Length);
var destination = initialBuffer.Length >= maxLength
? initialBuffer
: (toReturn = ArrayPool<char>.Shared.Rent(maxLength));
var bytesWritten = encoding.GetChars(source, destination);
return toReturn is null
? new Buffer<char>(destination, bytesWritten)
: new Buffer<char>(toReturn, bytesWritten);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment