[C#] Improve Span<> utilization (#8588)

There are a couple instances where the ByteBuffer's Span property was accessed in a loop.
 + Extracted the use of the property outside of the loop to save a few cpu cycles.

Access to the allocator's internal buffer isn't exposed as a ReadOnlySpan<byte> from the ByteBuffer
or the FlatBufferBuilder.
 + Added a few convenience functions to access the buffer using a ReadOnlySpan<byte>.

There are a few cases where built in Span extensions can be used to run optimized code.
 + Added the use of Span.Fill() and ReadOnlySpan.SequenceCompareTo to replace existing loops.

Co-authored-by: Björn Harrtell <bjornharrtell@users.noreply.github.com>
Co-authored-by: Wouter van Oortmerssen <aardappel@gmail.com>
This commit is contained in:
bigjt
2025-08-28 19:06:18 -04:00
committed by GitHub
parent a6b337f803
commit 82396fa0fe
3 changed files with 45 additions and 6 deletions

View File

@@ -275,6 +275,11 @@ namespace Google.FlatBuffers
}
#if ENABLE_SPAN_T && UNSAFE_BYTEBUFFER
public ReadOnlySpan<byte> ToSizedReadOnlySpan()
{
return _buffer.ReadOnlySpan.Slice(Position, Length - Position);
}
public ReadOnlyMemory<byte> ToReadOnlyMemory(int pos, int len)
{
return _buffer.ReadOnlyMemory.Slice(pos, len);
@@ -289,6 +294,11 @@ namespace Google.FlatBuffers
{
return _buffer.Span.Slice(pos, len);
}
public ReadOnlySpan<byte> ToReadOnlySpan(int pos, int len)
{
return _buffer.ReadOnlySpan.Slice(pos, len);
}
#else
public ArraySegment<byte> ToArraySegment(int pos, int len)
{
@@ -380,18 +390,19 @@ namespace Google.FlatBuffers
#elif ENABLE_SPAN_T
protected void WriteLittleEndian(int offset, int count, ulong data)
{
Span<byte> span = _buffer.Span.Slice(offset, count);
if (BitConverter.IsLittleEndian)
{
for (int i = 0; i < count; i++)
{
_buffer.Span[offset + i] = (byte)(data >> i * 8);
span[i] = (byte)(data >> i * 8);
}
}
else
{
for (int i = 0; i < count; i++)
{
_buffer.Span[offset + count - 1 - i] = (byte)(data >> i * 8);
span[count - 1 - i] = (byte)(data >> i * 8);
}
}
}
@@ -399,19 +410,20 @@ namespace Google.FlatBuffers
protected ulong ReadLittleEndian(int offset, int count)
{
AssertOffsetAndLength(offset, count);
ReadOnlySpan<byte> span = _buffer.ReadOnlySpan.Slice(offset, count);
ulong r = 0;
if (BitConverter.IsLittleEndian)
{
for (int i = 0; i < count; i++)
{
r |= (ulong)_buffer.Span[offset + i] << i * 8;
r |= (ulong)span[i] << i * 8;
}
}
else
{
for (int i = 0; i < count; i++)
{
r |= (ulong)_buffer.Span[offset + count - 1 - i] << i * 8;
r |= (ulong)span[count - 1 - i] << i * 8;
}
}
return r;
@@ -445,8 +457,7 @@ namespace Google.FlatBuffers
{
AssertOffsetAndLength(offset, sizeof(byte) * count);
Span<byte> span = _buffer.Span.Slice(offset, count);
for (var i = 0; i < span.Length; ++i)
span[i] = value;
span.Fill(value);
}
#else
public void PutSbyte(int offset, sbyte value)

View File

@@ -957,6 +957,21 @@ namespace Google.FlatBuffers
return _bb.ToSizedArray();
}
#if ENABLE_SPAN_T && UNSAFE_BYTEBUFFER
/// <summary>
/// A utility function return the ByteBuffer data as a
/// `ReadOnlySpan<byte>`.
/// </summary>
/// <returns>
/// A `ReadOnlySpan<byte>` that references the internal
/// ByteBuffer data.
/// </returns>
public ReadOnlySpan<byte> SizedReadOnlySpan()
{
return _bb.ToSizedReadOnlySpan();
}
#endif
/// <summary>
/// Finalize a buffer, pointing to the given `rootTable`.
/// </summary>

View File

@@ -183,6 +183,12 @@ namespace Google.FlatBuffers
var len_2 = bb.GetInt(offset_2);
var startPos_1 = offset_1 + sizeof(int);
var startPos_2 = offset_2 + sizeof(int);
#if ENABLE_SPAN_T && UNSAFE_BYTEBUFFER
var span_1 = bb.ToReadOnlySpan(startPos_1, len_1);
var span_2 = bb.ToReadOnlySpan(startPos_2, len_2);
return span_1.SequenceCompareTo(span_2);
#else
var len = Math.Min(len_1, len_2);
for(int i = 0; i < len; i++) {
byte b1 = bb.Get(i + startPos_1);
@@ -191,6 +197,7 @@ namespace Google.FlatBuffers
return b1 - b2;
}
return len_1 - len_2;
#endif
}
// Compare string from the ByteBuffer with the string object
@@ -200,6 +207,11 @@ namespace Google.FlatBuffers
var len_1 = bb.GetInt(offset_1);
var len_2 = key.Length;
var startPos_1 = offset_1 + sizeof(int);
#if ENABLE_SPAN_T && UNSAFE_BYTEBUFFER
ReadOnlySpan<byte> span = bb.ToReadOnlySpan(startPos_1, len_1);
ReadOnlySpan<byte> keySpan = key;
return span.SequenceCompareTo(keySpan);
#else
var len = Math.Min(len_1, len_2);
for (int i = 0; i < len; i++) {
byte b = bb.Get(i + startPos_1);
@@ -207,6 +219,7 @@ namespace Google.FlatBuffers
return b - key[i];
}
return len_1 - len_2;
#endif
}
}
}