From 82396fa0fe9a61e7a30bdd008e180d56f5e49ebf Mon Sep 17 00:00:00 2001 From: bigjt <208875523+bigjt-dev@users.noreply.github.com> Date: Thu, 28 Aug 2025 19:06:18 -0400 Subject: [PATCH] [C#] Improve Span<> utilization (#8588) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 from the ByteBuffer or the FlatBufferBuilder. + Added a few convenience functions to access the buffer using a ReadOnlySpan. 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 Co-authored-by: Wouter van Oortmerssen --- net/FlatBuffers/ByteBuffer.cs | 23 +++++++++++++++++------ net/FlatBuffers/FlatBufferBuilder.cs | 15 +++++++++++++++ net/FlatBuffers/Table.cs | 13 +++++++++++++ 3 files changed, 45 insertions(+), 6 deletions(-) diff --git a/net/FlatBuffers/ByteBuffer.cs b/net/FlatBuffers/ByteBuffer.cs index 5040d3463..0e959aec9 100644 --- a/net/FlatBuffers/ByteBuffer.cs +++ b/net/FlatBuffers/ByteBuffer.cs @@ -275,6 +275,11 @@ namespace Google.FlatBuffers } #if ENABLE_SPAN_T && UNSAFE_BYTEBUFFER + public ReadOnlySpan ToSizedReadOnlySpan() + { + return _buffer.ReadOnlySpan.Slice(Position, Length - Position); + } + public ReadOnlyMemory 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 ToReadOnlySpan(int pos, int len) + { + return _buffer.ReadOnlySpan.Slice(pos, len); + } #else public ArraySegment 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 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 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 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) diff --git a/net/FlatBuffers/FlatBufferBuilder.cs b/net/FlatBuffers/FlatBufferBuilder.cs index afe168352..3932f4bc3 100644 --- a/net/FlatBuffers/FlatBufferBuilder.cs +++ b/net/FlatBuffers/FlatBufferBuilder.cs @@ -957,6 +957,21 @@ namespace Google.FlatBuffers return _bb.ToSizedArray(); } +#if ENABLE_SPAN_T && UNSAFE_BYTEBUFFER + /// + /// A utility function return the ByteBuffer data as a + /// `ReadOnlySpan`. + /// + /// + /// A `ReadOnlySpan` that references the internal + /// ByteBuffer data. + /// + public ReadOnlySpan SizedReadOnlySpan() + { + return _bb.ToSizedReadOnlySpan(); + } +#endif + /// /// Finalize a buffer, pointing to the given `rootTable`. /// diff --git a/net/FlatBuffers/Table.cs b/net/FlatBuffers/Table.cs index f3860bfea..d6d9d09fb 100644 --- a/net/FlatBuffers/Table.cs +++ b/net/FlatBuffers/Table.cs @@ -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 span = bb.ToReadOnlySpan(startPos_1, len_1); + ReadOnlySpan 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 } } }