MDBValue potential improvements
Seeing your latest PR and calling AspSpan I wondered how MDBValue was designed.
Here is what Opus is suggesting:
MDBValue Performance Optimizations
This document outlines potential performance optimizations for the MDBValue struct in LightningDB using modern .NET marshalling and memory helpers.
Current Implementation
The existing MDBValue struct is a managed version of the native MDB_val type, designed to be blittable for P/Invoke marshalling.
Proposed Optimizations
1. Use MemoryMarshal for AsSpan()
The current implementation is efficient, but MemoryMarshal.CreateReadOnlySpan provides a more explicit approach:
using System. Runtime.InteropServices;
public ReadOnlySpan<byte> AsSpan() => MemoryMarshal.CreateReadOnlySpan(ref *data, (int)size);
2. Add a Writable Span Accessor
For scenarios requiring write access to the buffer:
public Span<byte> AsWritableSpan() => new Span<byte>(data, (int)size);
3. Add SkipLocalsInit Attribute
Skip zero-initialization for performance-critical structs:
using System.Runtime.CompilerServices;
[SkipLocalsInit]
public unsafe struct MDBValue
4. Add Typed Accessors with MemoryMarshal
For common use cases where the data type is known:
public T Read<T>() where T : unmanaged => MemoryMarshal. Read<T>(AsSpan());
public ReadOnlySpan<T> AsSpan<T>() where T : unmanaged => MemoryMarshal.Cast<byte, T>(AsSpan());
5. Use Unsafe.CopyBlockUnaligned for Bulk Copies
For frequent data copying scenarios:
using System.Runtime.CompilerServices;
public void CopyTo(Span<byte> destination)
{
Unsafe.CopyBlockUnaligned(ref MemoryMarshal.GetReference(destination), ref *data, (uint)size);
}
Complete Optimized Implementation
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace LightningDB;
[SkipLocalsInit]
public unsafe struct MDBValue
{
internal MDBValue(int bufferSize, byte* pinnedOrStackAllocBuffer)
{
size = bufferSize;
data = pinnedOrStackAllocBuffer;
}
//DO NOT REORDER
internal nint size;
//DO NOT REORDER
internal byte* data;
[MethodImpl(MethodImplOptions. AggressiveInlining)]
public ReadOnlySpan<byte> AsSpan() => new(data, (int)size);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Span<byte> AsWritableSpan() => new(data, (int)size);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public T Read<T>() where T : unmanaged => MemoryMarshal.Read<T>(AsSpan());
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ReadOnlySpan<T> Cast<T>() where T : unmanaged => MemoryMarshal.Cast<byte, T>(AsSpan());
public byte[] CopyToNewArray() => AsSpan().ToArray();
public void CopyTo(Span<byte> destination)
{
Unsafe.CopyBlockUnaligned(
ref MemoryMarshal.GetReference(destination),
ref *data,
(uint)size);
}
}
Key Benefits
| Optimization | Benefit |
|---|---|
AggressiveInlining |
Eliminates method call overhead by hinting the JIT to inline small methods |
SkipLocalsInit |
Avoids zero-initialization overhead (safe when fields are always assigned in constructor) |
| Generic typed accessors | Avoids manual casting and leverages MemoryMarshal for zero-copy reinterpretation |
Unsafe.CopyBlockUnaligned |
Can be faster than Span.CopyTo for known-size copies |
Considerations
- The struct layout must remain unchanged to maintain blittability for P/Invoke
-
SkipLocalsInitis safe here because all fields are assigned in the constructor - Generic methods with
unmanagedconstraint ensure type safety while enabling high-performance scenarios
Timely issue, this was going to be my next area to look into. Thanks @sebastienros
Hope you have some benchmarks available to test these. If you point to the repository Opus is able to read the source directly and understand the internal structures, or find gaps between the original API and the ports.
I'm already using claude code locally. I'll probably make a few passes at it. I'll be adding benchmarks for the new comparer options before releasing, yes.
Released in v0.21.0