[C#] FlatSpanBuffers: A Span-Centric FlatBuffers Runtime#8939
Draft
bigjt-dev wants to merge 1 commit intogoogle:masterfrom
Draft
[C#] FlatSpanBuffers: A Span-Centric FlatBuffers Runtime#8939bigjt-dev wants to merge 1 commit intogoogle:masterfrom
bigjt-dev wants to merge 1 commit intogoogle:masterfrom
Conversation
FlatSpanBuffers is a span-centric rework of the C# FlatBuffers implementation that is separate from the net/FlatBuffers implementation. Reduces object allocation and gives the caller more control over memory managment. The isolated addition in net FlatSpanBuffers has its own flatc flag --csharp-spanbufs. The majority of IFlatbufferObject access patterns are unchanged. Highlights: + Targeting .NET 10 for use of 'allows ref struct' constraint' + Spans is used for buffer operations handling encoding/decoding with endianess support. + No arrays, only spans. + More structs, fewer classes to avoid extra object allocations. + No AllowUnsafeBlocks required. + No preprocessor defines. No ENABLE_SPAN_T / UNSAFE_BYTEBUFFER. + Unit tests based on the the existing FlatBuffers.Test project.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
I have been working with FlatBuffers in .NET to quickly process data off the wire. While the net/FlatBuffers package is good, I wanted to reduce object allocations and use Spans to read and write buffers with stackalloc'd memory. I reworked the library and generated code, but kept the IFlatBufferObject API mostly the same.
These changes worked well for me. With a bit more work I could see this rework being adopted here too as an alternative to --csharp. Opening up the discussion with this draft PR.
I've highlighted the key parts of the runtime and IDL changes below. Most of the committed additions are from generated code.
Highlights
ref structandallows ref structSpan<T>can only be a member ofref structtypes.ByteSpanBufferandFlatSpanBufferBuildermust beref structto support accepting a Span buffer and store it as a member.Using
ref structtypes along with the array backedByteBuffercreated some code reuse challenges and required an elevation to .NET 9 to useallows ref structfor generic type constraints. This lets a single generic function accept both a regular struct and a ref struct without boxing, virtual dispatch, or core logic duplication.Where
allows ref structcould not be applied cleanly, I intentionally duplicated some structs for simplicity and reduced overhead, but extracted common functions as much as possible.Folder Structure
FlatSpanBuffers lives alongside the existing
net/FlatBufferscode and does not modify or reference it. The new code is self-contained:File Notes
BufferOperations.csSerialization functions that operate on
Span<byte>buffers. Handles the FlatBuffer type validation checks with minimal overhead. Addressed endian swaps for scalar vectors.TableOperations.csCommon functions for table field access shared across
TableandTableSpan. Uses genericallows ref structparameters so the same logic serves bothByteBufferandByteSpanBuffer. Contains the binary search (__lookup_by_key) that was previously provided only in the generated code.ByteBuffer.csNow a
struct. Wraps abyte[]with span-based access. Removed allocator reference to make struct lighter weight, especially when only used to read/decode.ByteSpanBuffer.csThe ref struct counterpart to
ByteBuffer. Wraps aSpan<byte>directly, enabling use ofstackalloc'd memory.BufferBuilder.csCommon BufferBuilder operations for FlatBuffer construction using
FlatBufferBuilderandFlatSpanBufferBuilder. Manages the allocator and handles buffer growth/reallocation.FlatBufferBuilder.csUses the array-backed
ByteBuffer, familiar api, serves as pass through to BufferBuilder functions. Reduced number of functions using generics.FlatSpanBufferBuilder.csThe ref struct builder that supports building from a
ByteSpanBufferbuffer. Requires more care in setup, but gives more control of allocations to the caller.VectorsVector wrapper structs to to avoid checking for buffer presence per element access.
FlatBufferVerify.csThe
Verifieris now aref struct, operating directly on span-backed data.RefStructNullable.csSince
Nullable<T>cannot wrap aref struct,RefStructNullable<T>provides.HasValue/.Valuesemantics for optional fields.IDL Gen Notes
ByteBuffer and SpanBuffer Objects
Each table/struct haw two namespaces:
MyGame.Example: works withByteBufferandFlatBufferBuilderMyGame.Example.StackBuffer: works withByteSpanBufferandFlatSpanBufferBuilderObject API Optimizations
PackandUnPackhave been reworked to reduce allocations:UnPackworks from bothByteBufferandByteSpanBufferConsistent Use of Nullable
Optional fields consistently use nullable semantics in both variants. ref struct-typed fields return
RefStructNullable<T>.System.Text.Json over Newtonsoft
JSON serialization attributes and converters have been migrated from
Newtonsoft.JsontoSystem.Text.Json.Serialization. Union types useJsonConverterimplementations.Benchmark Results
All benchmarks compare three implementations: the original
Google.FlatBuffers,FlatSpanBuffers, andFlatStackBuf. Benchmarks project defined ENABLE_SPAN_T;UNSAFE_BYTEBUFFER.For encoding tests, builders are preallocated to exclude allocations from the performance differences between the types to focus on internal improvements.
SimpleMonsterBenchmarks
DecodeBenchmarks
ObjectApiDecodeBenchmarks
EncodeBenchmarks
ObjectApiEncodeBenchmarks
VerifyBenchmarks
Improvements
API Examples
Building with FlatBufferBuilder
Building with FlatSpanBufferBuilder
Decoding Vectors
Mutable
Additional Thoughts
While FlatSpanBuffers targets .NET 10, some of the changes here could be applied to
net/FlatBuffers.For example:
ByteBufferBufferOperationsThis library could also be reworked to support netstandard2.1, but the 'StackBuffers and allows ref struct' approach would likely need to be discarded.
Based on some of the issues and pull requests I've seen, I presume some general interest in this rework. Let me know your thoughts!