Inlining and structs in C# #146
Replies: 2 comments 6 replies
-
|
Another reason why they are sort of evil is because the compiler is unable to use a number of optimizations that depend on a value not changing in the middle of an operation; otherwise a stale read is possible. And this potential for a value change is not just multi threading scenarios. There are many examples where a compiler can't rule out that a single method won't cause a value change as a side effect. Examples of compiler optimizations that can't be used include hoisting, enregistering values, simplification of arithmetic and others. And for a mutable struct, passing a value by |
Beta Was this translation helpful? Give feedback.
-
|
By the way, I found a niche use case for InlineArray structs that I never considered before. I wanted to see if this crossed your mind before. public sealed class DbStreamQuery(IDapperProvider dapperProvider, ServiceResultStatusAccessor accessor, ILogger<DbStreamQuery> logger): IDbStreamQuery
{
public IAsyncEnumerable<T> StreamQuery<T>(EmbeddedQuery query, CancellationToken ct) where T : class => StreamQuery<T>(query, Size128, ct);
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
public IAsyncEnumerable<T> StreamQuery<T>(EmbeddedQuery query, BufferSize bufferSize, CancellationToken ct) where T : class => bufferSize switch
{
Size128 => StreamQueryWithBuffer<T, Buffer128<T>, InlineArray128<T>>(query, ct),
Size256 => StreamQueryWithBuffer<T, Buffer256<T>, InlineArray256<T>>(query, ct),
Size512 => StreamQueryWithBuffer<T, Buffer512<T>, InlineArray512<T>>(query, ct),
Size64 => StreamQueryWithBuffer<T, Buffer64<T>, InlineArray64<T>>(query, ct),
_ => throw new ArgumentOutOfRangeException(nameof(bufferSize), bufferSize, null)
};
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
private async IAsyncEnumerable<T> StreamQueryWithBuffer<T, TBuffer, TArray>(EmbeddedQuery query, [EnumeratorCancellation] CancellationToken ct)
where TBuffer : class, IBuffer<TArray, TBuffer, T>, new()
where TArray : struct, IInlineArray<TArray, T>
where T : class
{
var buffer = new TBuffer();
await using var connection = dapperProvider.GetEscherDbConnection();
await using var e = dapperProvider.GetStreamFromQuery<T>(connection, query).GetAsyncEnumerator(ct);
var hasMore = true;
loadBuffer:
try
{
// Keep loading buffer in quick succession until full or no more items
while (!buffer.IsFull)
{
hasMore = await e.MoveNextAsync();
// No more items -- flush what we have in buffer
if (!hasMore) break;
// should always return `true` since we check for IsFull above
var wasAdded = buffer.TryAdd(e.Current);
Debug.Assert(wasAdded, "buffer.TryAdd(e.Current) returned false. This should not happen.");
}
}
catch (Exception ex)
{
logger.LogEscherQueryFailed(query, ex);
accessor.ServiceResultStatus = ResultStatus.Failed;
throw;
}
// Empty the buffer of all items before loading it again
foreach (var item in buffer) yield return item;
if (hasMore)
{
// Arrives here if the buffer was full but there are still more items to read
buffer.Clear();
// Continue loading the buffer
goto loadBuffer;
}
}
}[InlineArray(ArraySize)]
public struct InlineArray512<T> : IInlineArray<InlineArray512<T>, T>
{
const int ArraySize = 512;
public static int Size => ArraySize;
private T _element0;
[UnscopedRef, MethodImpl(AggressiveInlining)]
public ref T At(int i) => ref this[i];
public ref T Head
{
[UnscopedRef, MethodImpl(AggressiveInlining)]
get => ref DangerousGetElementAt(0);
}
public ref T Tail
{
[UnscopedRef, MethodImpl(AggressiveInlining)]
get => ref DangerousGetElementAt(ArraySize - 1);
}
[MethodImpl(AggressiveInlining)]
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref Head, ArraySize);
[EditorBrowsable(EditorBrowsableState.Never)]
public ref T DangerousGetElementAt(int index) => ref Unsafe.Add(ref Unsafe.AsRef(in _element0), index);
}
public sealed class Buffer512<T> : IBuffer<InlineArray512<T>, Buffer512<T>, T>, IBuffer<T>
{
private InlineArray512<T> _inlineBuffer;
public int MaxSize { get; } = InlineArray512<T>.Size;
public int Count { get; private set; }
public bool IsFull
{
[MethodImpl(AggressiveInlining)]
get => Count >= MaxSize;
}
[MethodImpl(AggressiveInlining)]
public ref InlineArray512<T> GetInternalBufferByRef() => ref _inlineBuffer;
[MethodImpl(AggressiveInlining)]
public void Clear() => Count = 0;
[MethodImpl(AggressiveInlining)]
public bool TryAdd(T item)
{
if (IsFull) return false;
_inlineBuffer.At(Count++) = item;
return true;
}
[MethodImpl(AggressiveInlining)]
public ref T DangerousGetReference(int index) => ref _inlineBuffer.DangerousGetElementAt(index);
[MethodImpl(AggressiveInlining)]
public Span<T> AsSpan() => _inlineBuffer.AsSpan()[..Count];
[MethodImpl(AggressiveInlining)]
public BufferEnumerator<T> GetEnumerator() => new(this);
} |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
-
Inlining and structs in C#
In this - somewhat technical and barely usable - blog post, we will have a look at inlining and structs in C#. And how they can optimize performance in some interesting ways.
https://steven-giesel.com/blogPost/e89d7156-f3fd-4152-b78a-cb908bc43226/inlining-and-structs-in-c
Beta Was this translation helpful? Give feedback.
All reactions