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
-
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 useflatc
as 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:flatc
andflatcc
.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.Runtime
andFlatSharp.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.Runtime
package 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_nonVirtual
DeprecationIn 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...Lazy
deserialization. Accessing an Array property on aLazy
object simply allocates a new array each time. Not good!Lazy
,Greedy
, andProgressive
modesfs_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
required
members. That is, when you specify therequired
FBS 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_GREATER
peppered 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.Runtime
package.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 bothOuter1
andOuter2
. 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
IInputBuffer
implementations 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 aLazy
vector 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_case
fields 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
csproj
file: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 false
as 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.Vector3
from a FlatSharp schema:In this example, the
Points
property in thePath
table 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.flatc
orflatcc
.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
MyUnsafeUnion
to 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_unsafeUnion
will raise an error if there are any reference types in the union. Experimentally, performance degrades significantly when a union carries a mix ofobject
andfixed byte[]
elements.fs_unsafeExternal
value structs.fs_unsafeExternal
value 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.Compiler
package, so upgrading should be straightforward.FlatSharp Package (Runtime Mode)
FlatSharp.Runtime Package
IInputBuffer2
has been deleted and merged intoIInputBuffer
. Assorted changes have been made toIInputBuffer
.Switch
methods on Unions have been deleted. Please use the newAccept
/Visitor
pattern instead.ISerializer<T>.WithSettings
now accepts a delegate instead of a settings object. This change is largely semantic but allows easier usage.ISerializer<T>
propertiesCSharp
,Assembly
, andAssemblyBytes
have been removed.ISerializer<T>.Parse
now accepts an optionalDeserializationMode
argument.FlatSharp.Compiler Package
IList
,IReadOnlyList
,IIndexedVector
,Memory
, andReadOnlyMemory
.fs_nonVirtual
attribute no longer has any meaning. All properties are virtual now.ubyte
now default to usingMemory
instead ofIList
if no type is specified.--input
parameter now accepts a semicolon-delimited list of files:dotnet FlatSharp.Compiler.dll --input "file1.fbs;file2.fbs;file3.fbs" --output .
FlatSharp.generated.cs
instead ofSchemaName.fbs.generated.cs
. Note: You may experience build errors after upgrading due to type conflicts. Be sure and clean yourobj
folder 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