7.0.0 #347
jamescourtney
announced in
Announcements
7.0.0
#347
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
-
Welcome to FlatSharp 7! Version 7 is a major release that includes many under-the-hood improvements such as .NET 7 support, performance improvements for several cases, generated code deduplication, experimental object pools, and some big breaking changes. This is a long list of changes, so buckle up!
Runtime-Only Mode Deprecation
Let's start with the bad news: Reflection-only Runtime mode is not making the jump to FlatSharp 7.
The original version of FlatSharp did not include a compiler or support for FBS files. Instead, it used attribute-based reflection like so many other .NET serializers do with attributes like
[FlatBufferTable]which allowed generation of code at runtime. FlatSharp.Compiler was introduced in early 2020, and in version 6 it was ported to useflatcas its parser instead of the custom grammar. Today, there is really no reason to use reflection mode any longer. The FlatSharp compiler is mature and enables all of the same semantics, with a slew of additional benefits over runtime mode:flatcandflatcc.For these reasons, the trend has been that most new features and development of FlatSharp use the compiler rather than the reflection-only mode. So in version 7, FlatSharp is dropping support for runtime code generation. This is a difficult decision, but one that allows the project to keep moving forward and is broadly aligned to most customer's usage of FlatSharp, along with the bigger trends in .NET, such as AOT and source generators. Going forward, only two packages will see new versions published:
FlatSharp.RuntimeandFlatSharp.Compiler.Removing Runtime-only mode removes an entire class of human error from FlatSharp and makes AOT easier to reason about. One immediate benefit of this change is that the
FlatSharp.Runtimepackage now has no internal reflection calls any longer, which makes AOT less error-prone. Unfortunately, it does also remove support for some features such as Type Facades.Array Vector and
fs_nonVirtualDeprecationIn the interest of helping engineers make good decisions about how to use FlatSharp, Array vectors (
fs_vector:"Array") and non-virtual properties have been deprecated. These behave very unpredictably with FlatSharp, because...Lazydeserialization. Accessing an Array property on aLazyobject simply allocates a new array each time. Not good!Lazy,Greedy, andProgressivemodesfs_writeThrough)IList<T>, provide much of the performance, with an order of magnitude more flexibility.When serializing Vectors, FlatSharp does still attempt to devirtualize
IList<T>andIReadOnlyList<T>to arrays for the performance boost, but this is more an implementation detail of the handling forIList<T>than it is about continuing to support arrays explicitly..NET 7 Support
FlatSharp version 7 supports .NET 7. What a happy coincidence! This is not a major change. However, there are a few things worth calling out.
The first is that
IFlatBufferSerializable<T>has a few new members when using .NET 7:This allows writing code like this:
Next, FlatSharp 7 supports
requiredmembers. That is, when you specify therequiredFBS attribute, the generated C# also contains that annotation:Now generates:
This is, of course, only available for those of you using .NET 7. If you aren't ready for .NET 7 just yet, then no problem; FlatSharp will continue to generate code that works with .NET 6, .NET Standard 2.0, and .NET Standard 2.1, though you may see
#if NET7_0_OR_GREATERpeppered throughout your generated code now!Finally, FlatSharp 7 fully supports .NET 7 AOT. Effort has been made to remove the last vestiges of reflection from the
FlatSharp.Runtimepackage.Code Deduplication
Previous versions of FlatSharp generated separate code for each root type. That is, imagine this schema:
In this case, FlatSharp 6 and below would generate 2 full serializers and parsers for
Common, since it is used by bothOuter1andOuter2. This led to cases where commonly-used large tables would have more than one serializer implementation generated. Such code duplication leads to poor cache performance, poor utilization of the branch predictor, and an explosion of code when the type in question is used in multiple places, such as gRPC definitions.FlatSharp 7 does the expected thing and generates only one serializer/parser for each type. As part of this change, FlatSharp 7 emits all serializer types, which comes with the happy accident of allowing you to specify it at runtime:
Performance Improvements
FlatSharp 7 improves the performance of
IList<T>vectors by enabling devirtualization of internal method calls. Previous versions of FlatSharp defined the base vector along these lines:Virtual methods do have a cost, because the assembly must look first to the vtable to then jump to the actual methods. A better way to write the same code is with this technique:
But wait! Aren't these the same thing? Both are calling a virtual method after all. The trick is subtle, and involves generics and structs. Stephen Cleary writes about it more clearly than I can here.
This technique is not new to FlatSharp. Support for this trick was added in Version 4, and why
IInputBufferimplementations have been structs and the entire Parsing stack is templatized. However, the opportunity to do the same for vectors was only recently discovered, and the improvements are impressive. Iterating through aLazyvector is often 20-50% faster.Additionally, VTable parsing has been improved by several whole nanoseconds! Joking aside, this is an important thing since every table has a VTable, so this is one of the operations that FlatSharp does at the very core, and the benefit multiplies with the number of tables you read. Only tables with 8 or fewer fields benefit from this optimization. Larger tables fall back to the previous behavior.
Here's a quick teaser of FlatSharp parse/traverse performance of a vector of structs, both reference and value:
Object Pooling
Object Pooling is a technique that can be used to reduce allocations by returning them to a pool and re-initializing them later. FlatSharp 7's object pool is experimental. The intent of this release is to get the feature into the wild and how well it works. With Object Pooling enabled, it is possible to use FlatSharp in true zero-allocation mode. The wiki has full details.
Field Name Normalization
In version 6.2.0, FlatSharp introduced an optional switch to normalize
snake_casefields intoUpperPascalCase. This was off by default in version 6 for compatibility reasons. In FlatSharp 7, field name normalization is on by default. There are three ways to disable this:Add this to your
csprojfile:Annotate your tables and structs with
fs_literalName:Finally, if you use the FlatSharp compiler as a command line tool, you can pass
--normalize-field-names falseas a command line argument.Unsafe External Value Structs
FlatSharp 7 adds a new annotation (
fs_unsafeExternal) that may be applied to value structs and enums. The annotation indicates to FlatSharp that the type is defined externally to the FBS Schema. Serializers are generated based on the definition, but no type definitions are emitted. This allows doing things like referencing hardware accelerated types such asSystem.Numerics.Vector3from a FlatSharp schema:In this example, the
Pointsproperty in thePathtable will be of typeIList<System.Numerics.Vector3>. There are a few catches to this that you need to be aware of. The first is that it's not safe. FlatSharp is able to provide exactly two validations:There are many additional things beyond the size of the struct and the endianness of the machine that FlatSharp cannot validate with external structs:
System.Numerics.Vector<byte>.Vector<byte>is 32 bytes when running under JIT on a machine supporting AVX2. In the future it maybe 64 bytes on an AVX512 machine. Or it might be 16 bytes if using .NET 7 AOT compilation.flatcorflatcc.External structs can be a powerful tool, but they are risky and need thorough testing to ensure correct behavior. If in doubt, it is advisable to simply extend the partial struct declarations and add an implicit conversion operator to the value struct in question.
Unsafe Unions
In other Unsafe news, FlatSharp 7 supports unsafe unions. Unsafe unions are optimizations that apply to unions consisting only of value types. Imagine this FBS schema:
Previous versions of FlatSharp would box those value types into an object inside the union:
Unsafe unions store their data as a fixed array corresponding to the size of the largest element:
This allows
MyUnsafeUnionto carry all of the values for all of the value types in a single fixed element, without the need for boxing or allocations! There are a couple of caveats to this feature:fs_unsafeUnionwill raise an error if there are any reference types in the union. Experimentally, performance degrades significantly when a union carries a mix ofobjectandfixed byte[]elements.fs_unsafeExternalvalue structs.fs_unsafeExternalvalue structs, you can get runtime exceptions.<AllowUnsafeBlocks>true</AllowUnsafeBlocks>Unsafe unions offer reduced allocations and increased performance when traversing a vector:
Breaking Changes
Even though FlatSharp 7 is a major release, there are not a ton of breaking changes if you are using the
FlatSharp.Compilerpackage, so upgrading should be straightforward.FlatSharp Package (Runtime Mode)
FlatSharp.Runtime Package
IInputBuffer2has been deleted and merged intoIInputBuffer. Assorted changes have been made toIInputBuffer.Switchmethods on Unions have been deleted. Please use the newAccept/Visitorpattern instead.ISerializer<T>.WithSettingsnow accepts a delegate instead of a settings object. This change is largely semantic but allows easier usage.ISerializer<T>propertiesCSharp,Assembly, andAssemblyByteshave been removed.ISerializer<T>.Parsenow accepts an optionalDeserializationModeargument.FlatSharp.Compiler Package
IList,IReadOnlyList,IIndexedVector,Memory, andReadOnlyMemory.fs_nonVirtualattribute no longer has any meaning. All properties are virtual now.ubytenow default to usingMemoryinstead ofIListif no type is specified.--inputparameter now accepts a semicolon-delimited list of files:dotnet FlatSharp.Compiler.dll --input "file1.fbs;file2.fbs;file3.fbs" --output .FlatSharp.generated.csinstead ofSchemaName.fbs.generated.cs. Note: You may experience build errors after upgrading due to type conflicts. Be sure and clean yourobjfolder of generated files first.New Contributors
Full Changelog: 6.3.3...7.0.0
This discussion was created from the release 7.0.0.
Beta Was this translation helpful? Give feedback.
All reactions