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

View File

@@ -957,6 +957,21 @@ namespace Google.FlatBuffers
return _bb.ToSizedArray(); 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> /// <summary>
/// Finalize a buffer, pointing to the given `rootTable`. /// Finalize a buffer, pointing to the given `rootTable`.
/// </summary> /// </summary>

View File

@@ -183,6 +183,12 @@ namespace Google.FlatBuffers
var len_2 = bb.GetInt(offset_2); var len_2 = bb.GetInt(offset_2);
var startPos_1 = offset_1 + sizeof(int); var startPos_1 = offset_1 + sizeof(int);
var startPos_2 = offset_2 + 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); var len = Math.Min(len_1, len_2);
for(int i = 0; i < len; i++) { for(int i = 0; i < len; i++) {
byte b1 = bb.Get(i + startPos_1); byte b1 = bb.Get(i + startPos_1);
@@ -191,6 +197,7 @@ namespace Google.FlatBuffers
return b1 - b2; return b1 - b2;
} }
return len_1 - len_2; return len_1 - len_2;
#endif
} }
// Compare string from the ByteBuffer with the string object // 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_1 = bb.GetInt(offset_1);
var len_2 = key.Length; var len_2 = key.Length;
var startPos_1 = offset_1 + sizeof(int); 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); var len = Math.Min(len_1, len_2);
for (int i = 0; i < len; i++) { for (int i = 0; i < len; i++) {
byte b = bb.Get(i + startPos_1); byte b = bb.Get(i + startPos_1);
@@ -207,6 +219,7 @@ namespace Google.FlatBuffers
return b - key[i]; return b - key[i];
} }
return len_1 - len_2; return len_1 - len_2;
#endif
} }
} }
} }