Repository navigation
v4.0.0-rc.8
Pre-releaseInstall: dotnet add package Nino --version 4.0.0-rc.8 (Unity package: 4.0.0-preview.208)
Other languages: go get github.com/JasonXuDeveloper/Nino/runtimes/go/v4@v4.0.0-rc.8, cargo add nino-serialization@=4.0.0-rc.8, npm install nino-serialization@4.0.0-rc.8.
Wire format
Only these write different bytes than v4.0.0-rc.7:
- On 32-bit x86, a member whose type is a struct (not a
[NinoType]) holding aTimeOnly,Complex,Int128orUInt128after a smaller field is now written in the canonical layout, as on 64-bit runtimes, instead of its x86 memory. - Types with a member that is a tuple of eight items or more now write it, every item in order as for shorter tuples, where they wrote nothing.
T?[]and otherT?sequences of a raw[NinoType]struct whose formatter the runtime resolver builds (types no generated code names, serialized byTypeor boxed) are written at the fixed stride generated code uses, so either side reads the other's bytes; payloads such a runtime-bound formatter wrote before must be rewritten.- Unmanaged structs that are not
[NinoType]and hold anInt128orUInt128now write their padding as zeros on .NET 8 and later, as the canonical layout requires (it was copied from memory, so it held whatever was there). - A closed generic
[NinoType]struct (Pad<int>) held as a member or collection element is written member by member, as its formatter writes it everywhere else, unless its definition is raw; it was copied as raw memory with its padding there. - Every
stringmember and element (andStringBuilder,TimeZoneInfo's id,[NinoUtf8]strings) is written with a 1, 3 or 5 byte header instead of a 4-byte one: null is00,""is01,"abc"is07 61 62 63, and a string of 64 to 32,767 chars starts with80and a 2-byte value. Strings up to 63 chars save 3 bytes each: 1,000 mixed short strings take 20,848 bytes instead of 23,848 ([NinoUtf8]: 21,884 instead of 27,884). Data written by earlier 4.0 prereleases is not readable. - The TypeScript runtime (the nino.ts that nino-gen writes) writes and reads strings with the 1, 3 or 5 byte headers .NET writes from rc.8 on; data written by earlier versions is not readable.
- The Go runtime writes and reads strings with the 1, 3 or 5 byte headers .NET writes from rc.8 on; data written by earlier versions of the module is not readable.
- The Rust runtime (nino-serialization) writes and reads strings with the 1, 3 or 5 byte headers .NET writes from rc.8 on; data written by earlier versions of the crate is not readable.
- On 32-bit x86 .NET (Windows), raw structs (plain structs, and
[NinoType]structs whose layout differs between runtimes) holding aDateTimeat an offset the canonical layout pads are now written in the canonical layout, as on 64-bit runtimes, instead of their x86 memory. - On .NET Framework (64-bit and 32-bit), raw structs holding a
DateTimewhose fields the runtime reorders are now written in the canonical layout, as on 64-bit .NET, instead of their memory.
New
- A Go runtime (
runtimes/go, packagenino) that reads and writes Nino's binary format, for code generated or written in the style of the TypeScript runtime. - Struct tags for the Go runtime:
nino.Marshalandnino.Unmarshalread and write tagged Go structs without generated code, checked against the .NET layout a type declares withNinoLayout(). - A Rust runtime (
runtimes/rust, not yet published) reads and writes Nino's wire format, deep nesting and version-tolerant and keyed types included, with no unsafe code and no panics on any input. - Rust runtime: a
serdefeature implementing serde for Nino's value types, andFromStrforDecimal,GuidandBigInteger, so structs with Nino codecs can derive serde as well. - Rust runtime:
#[derive(NinoRead, NinoWrite)](featurederive) with#[nino(...)]attributes for the format, kind, type id, member order, keys and[NinoUtf8], checked against a published layout at compile time. - Warning NINO027 flags
[NinoFormerName]on a generic type that is written with a type id: every instantiation would share the one id and collide. NinoTypeLayoutAttribute.Deferrablesays whether a type's bodies nested past 128 class bodies are deferred, and the layout's text form carries it asdeferrable=true|false, so code generators for other languages read it instead of re-deriving .NET's rules.- Go and Rust read whether a type is deferrable from its published layout (
deferrable=true|false); the Rust derive'sdeferrableattribute is now optional whenlayoutis given. - Go code generation:
nino-gen --lang gowrites Go structs (withninotags andjson/yamltags, camelCase by default) and codecs for an assembly's[NinoType]s, for the Go runtime inruntimes/go. - Rust code generation:
nino-gen --lang rustwrites Rust structs and codecs for an assembly's[NinoType]s, for thenino-serializationcrate, with optional serde derives (--serde). nino-gen(.NET toolNino.Codegen): writes TypeScript, Go or Rust types and codecs for the[NinoType]s of a built assembly, from the layouts the generator publishes in it (nino-gen --assembly <dll> --lang ts|go|rust --out <dir>).NinoDataException(namespaceNino.Core, derived fromInvalidOperationException) is thrown for every invalid input Nino rejects while deserializing, so untrusted data can be caught with one specific type.NinoSettings.MaxDepth(default 512, at least 128) andNinoSettings.MaxCollectionLength(defaultint.MaxValue) bound nesting and the element count a collection in the data may claim; set them before the first serialization or deserialization (later they throw).NinoSettings.MaxHashCollisions(default 100): a key read from the data that finds more keys than this in its hash bucket throwsNinoDataException, so data built to make keys collide (hash flooding) can no longer make reading aDictionary,HashSet, lookup,ImmutableDictionaryorImmutableHashSetquadratic.NinoSettings.Untrusted(off by default): for data from someone who could craft it, the dictionaries and sets Nino creates hash integer,char,Guidandstringkeys with a per-process secret, raw structs are checked as their constructors would (bool,DateTime,decimal,DateOnly,TimeOnly), and bytes left after the value throwNinoDataException. Off, it costs nothing.- Generic
[NinoType]s with non-public members no longer need to bepartialwhen the project targets .NET 9 or later: generated code reaches the members through[UnsafeAccessor], as it does for non-generic types since .NET 8. - Nino.Codegen writes your
[NinoType]s as TypeScript, Go or Rust after each build: declare<NinoCodegen Include="typescript" Out="ts/" />(orgo,rust) with nino-gen's options as metadata. Thenino-gencommand is now the Nino.Codegen.Tool dotnet tool. Nino.Codegen replaces Nino.TypeScript, whoseNinoTypeScript*properties still work with a warning (NINOGEN003). - nino-gen generates generic
[NinoType]s as generic types in TypeScript, Go and Rust (Box<T>,Box[T any],BoxType<T>), with codecs built from those of their type arguments (BoxCodec(...)), and closed uses such asBox<int>as members. - nino-gen's
--maps indexmap|std(MSBuild metadataMaps) for Rust:stdwritesstd::collections::HashMapandHashSetinstead ofindexmap's, with no dependency; they are written inHashMap's iteration order.indexmapstays the default. - nino-gen generates closed generic types in polymorphic and
objectslots (a generic subtype such asBox<int> : Animal) for TypeScript, Go and Rust: the closed types the assembly's code names are read from its metadata, each a case of the slots it is assignable to, behind the id of its closed .NET name. - The Go, Rust and TypeScript runtimes are published with every release, with Nino's version:
go get github.com/JasonXuDeveloper/Nino/runtimes/go/v4(the module path now ends in/v4),cargo add nino-serialization(with thederivefeature for#[derive(NinoRead, NinoWrite)]) andnpm install nino-serialization(generate withRuntime="nino-serialization"to import it). - The Unity package includes optional Burst-compiled kernels (Nino.Burst): with com.unity.burst installed, arrays of tuples and structs, raw structs with padding and Latin-1 strings are moved with SSSE3 or AArch64 Neon instead of Nino's scalar netstandard paths, with the same bytes.
useWasm()in the TypeScript runtime: longer strings are read and written through a small embedded WebAssembly module in browsers and Deno, with the JavaScript paths kept where WebAssembly SIMD, GC or the JS String Builtins are unavailable or blocked;useWasm(false)turns it off.- Reference tracking (
trackReferences) in the TypeScript, Go and Rust runtimes, for the code nino-gen writes; Rust holds tracked objects asnino::Shared<T>. - nino-gen generates generic types written behind a type id (non-sealed, abstract, interface), the closed types their slots need,
NullableandKeyValuePairtype arguments inside collections, reference tracking (trackReferences) and[NinoType]collections for TypeScript, Go and Rust, and generic types holding themselves with other type arguments for TypeScript. deserializeExactin the TypeScript runtime (nino-serialization):deserializethat also throws aNinoErrorwhen bytes follow the value, as Go'sDeserializeExactand Rust'sdeserialize_exactdo.Nino.WebGLin the Unity package: WebGL players built with WebAssembly SIMD (Unity 6's Target WebAssembly 2023, or--compiler-flags=-msimd128on 2022.3) move Latin-1 strings of 16 chars or more and arrays of tuples and padded raw structs of 32 or more with wasm SIMD kernels, the same bytes as without them.NinoKernelsis now a sealed table of native function pointers (delegate* unmanaged[Cdecl]propertiesPermute,Narrow,Widenand the newNarrowAsciiandWidenAsciifor[NinoUtf8]strings, withMinBlocksandMinChars), whichNinoKernels.Registercopies into static fields so each kernel is one direct call; it replaces the abstract class of earlier builds, so a subclass of it must become a table ofcdeclfunctions.- nino-gen's
--numeric-arrays typed(NumericArrays="typed") generates arrays and lists of fixed-size numbers in TypeScript as typed arrays (Int32Array,Float64Array,BigInt64Array...), copied in one block: faster to serialize in Node and Chromium and to deserialize in Node, 2-3x slower to deserialize in Chromium, so the default staysnumber[]. - The MSBuild property
<NinoPrefetchLargeCollections>true</NinoPrefetchLargeCollections>: top-levelSerializeof aT[]orList<T>of a non-generic, non-fixed-size[NinoType]class, andDeserialize(data, ref collection)into one, prefetch the objects ahead once the collection outgrows a quarter of the last-level cache and its objects are not in allocation order (.NET 8+ on x64 and Arm64, through the interceptors; not Unity). The bytes are the same; unset, the generated code is unchanged. - Go:
Reader.EnterBody/ExitBodyandWriter.EnterBody/ExitBodyopen a class body in place for code that calls the body directly (falling back toReadClassInto/WriteClassfrom 128 open bodies on), andnino.UniqueEntrieschecks pairs read by your own loop for a repeated key; nino-gen's Go code uses both. - Go:
nino.UniqueValueschecks the elements of a set read by your own loop for a repeated value (the counterpart ofUniqueEntries); nino-gen's Go code reads sets with direct calls and checks them with it. - Rust
nino::deserialize_into(&bytes, &mut value)andNinoRead::read_intoread into an existing value as .NET'sDeserialize(data, ref value)does: nested objects read in place,Vecbuffers and elements reused, maps and sets cleared and refilled, polymorphic values reused only for the same type. - TypeScript
deserializeInto(codec, data, value)reads into an existing value as .NET'sDeserialize(data, ref value)does: nested objects read in place, arrays keep their storage and elements, maps and sets cleared and refilled, polymorphic members reused only for the same class. - Go
nino.DeserializeInto(data, &v, ReadXInto)reads into an existing value as .NET'sDeserialize(data, ref value)does: nested objects read in place, slices keep their storage and elements, polymorphic values reused only for the same type; nino-gen writesReadXIntofor every type. - Code fixes for NINO024 (remove a member's
[NinoFormerName]in a type that is not keyed) and NINO025 (remove a[NinoFactory]that[NinoConstructor]makes unused); NINO003, NINO005, NINO007, NINO010, NINO011, NINO012 and NINO017 now say what happens and what to change. - Rust:
nino::sealed_read!()andnino::sealed_write!()write a sealed class'sNinoReadandNinoWritemethods in a hand-written impl, as generated code does. - Go (breaking): keyed members are read and written with
BeginMember,EndMember,SkipMemberandKeyedMember, which replaceBeginEntry,EndEntry,SkipEntryandKeyedEntry; the duplicate checks areCheckUniqueKeys,CheckUniqueValuesandCheckUniqueStrings. Regenerate Go code with nino-gen and rename the calls in hand-written codecs. - Rust:
NinoWrite::PREFETCH_BYTESreplacesNinoWrite::PREFETCH, which still works, deprecated; the derive'sformattakes"positional"and"version_tolerant"("plain"and"tolerant"are still accepted). - nino-gen:
--numeric-arrays(NumericArrays) takesplain(the default,number[]) ortyped, instead ofarrayortyped. - Go (breaking):
ReadSlice,WriteSlice,ReadMap,WriteMap,ReadEntries,WriteEntries,ReadSet,WriteSet,ReadUnique,ReadUniqueEntries,ReadFixed,WriteFixed,WritePrimitivesRaw,ReadPrimitivesIntoandReadSealedIntoare removed: generated code no longer calls them. Regenerate Go code written by an older nino-gen, and write hand-written collection loops with direct calls. - Rust (breaking): the depth constants are
INLINE_DEPTHandMAX_DEPTH, which replaceDEPTH_INLINEandDEPTH_MAX. - TypeScript (breaking): the
NinoReaderandNinoWritermethods generated code calls arebeginRegionandexitBody, which replaceregionandexit; regenerate code an earlier nino-gen or Nino.TypeScript wrote. - TypeScript (breaking): the
NinoReadermethod generated code calls to read a presence byte ispresent(wasnotNull), withpresentAtfor a presence byte at a given offset, and the subtype registry isNinoSubtypes(wasNinoRegistry); regenerate code an earlier nino-gen or Nino.TypeScript wrote. - Renamed before 4.0:
Nino.Core.NinoPolymorphic<TBase>(4.0.0-rc.7), the subtype registry generated code calls, is nowNino.Core.CompilerServices.NinoSubtypes<TBase>; the user APINinoPolymorphic.Registerkeeps its name. - NINO028 warns about
[NinoMember]on a member Nino never serializes (static, constant, indexer, a property without a setter that no constructor takes, a backing field), where NINO010 wrongly said it was collected already; a code fix removes it. NINO008 now names the member and its type.
Performance
- TypeScript runtime in Chromium: a text-heavy article reads 27-31% faster on GitHub's x64 and Arm64 runners, and a list of mixed ASCII, Latin-1 and other strings writes about 12% faster on x64; Node is unchanged.
- Go runtime strings: writing mixed text is 1.9x faster on Arm64 (NEON, on par with MessagePack there) and 2.7x faster on x64 without AVX2 (SSSE3); reading it is 17% faster on Arm64 and 34% faster on x64 without AVX-512 VBMI (GitHub runners: Neoverse-N2, AMD EPYC 7763 with the tier forced).
- Unity x64 players on AVX2 CPUs: arrays of small tuples and structs (up to 16 bytes, 256 values or more) read up to 3.5 times and written up to 1.3 times as fast as with the SSSE3 kernels (IL2CPP, Burst AVX2 target).
- In Node, writing strings of up to 48 chars no longer goes through a regex and a native call: serializing 1000 ASCII strings takes 0.025-0.028 ms instead of 0.060-0.065 (x64 and Arm64), 1000 mixed strings 0.056-0.059 instead of 0.090, the Lookup case 0.39 ms instead of 0.74-0.78, OrderBook 0.43 instead of 0.76-0.79 and Player 0.26 instead of 0.31-0.34. Browsers are unchanged.
- Rust: writing strings with non-ASCII chars is up to 2.4x faster on x64 with AVX2 or AVX-512 (strings-mixed-1000 serialize 37% faster on an AVX-512 runner, 18% on an AVX2 one) and up to 5x on Arm64 for text over 64 bytes; reading Latin-1 and UTF-16 strings of 32 bytes or more is up to 2.4x faster on x64 and 1.6x on Arm64.
- Rust: [NinoUtf8] strings are written up to 45% faster and read 10% to 14% faster (strings-mixed-utf8-1000, x64 and Arm64 runners).
- TypeScript in Node: 1,000 CJK and Cyrillic strings of 4 to 16 chars serialize in 0.018 ms instead of 0.066 on x64 and 0.034 instead of 0.108 on Arm64 (69-73% less time) and deserialize 46-47% faster; CJK strings of 24 to 40 chars read and write 5-7% faster (GitHub's x64 and Arm64 runners).
- Go: strings are written and read faster on x64 and Arm64: a list of 1,000 short ASCII strings serializes in 33-40% less time and deserializes in 17-27% less, mixed strings serialize 5-14% faster (GitHub runners: AMD EPYC 7763, 9V74 and 9V45, Intel Xeon Platinum, Neoverse-N2).
- Go:
nino.Marshalandnino.Unmarshalof tagged structs with strings, string slices and maps take 52-76% less time to write and 18-45% less to read (the same runners). - Go: writing and reading
[NinoUtf8]strings that are not ASCII is faster on x64 and Arm64 (a third faster to write and a fifth faster to read a list of mixed strings on x64). - Rust: strings are written and read faster on x64 and Arm64: a list of 1,000 short ASCII strings serializes in 11-25% less time and deserializes in 3-6% less, mixed strings in 5-30% and 10-12% less (GitHub runners: AMD EPYC 7763, 9V74 and 9V45, Intel Xeon Platinum, Neoverse-N2).
- Rust: reading a struct or class with string members takes less time (9 to 15% for the benchmark's record).
- Writing a list of numbers or other fixed-size values from Rust takes 23 to 52% less time (10,000
i32orf64), and a record holding a short list of numbers up to 9% less. - Writing a record holding a short list of numbers from Rust takes 1 to 18% less time.
- On x64, writing records with a short ASCII string of 16 to 32 bytes from Rust takes about 4 to 7% less time (a list of 1,000 records).
- The TypeScript runtime (nino.ts) reads strings 16-25% faster in Node and mixed text 13-18% faster in Chromium, writes them 3-15% faster, and generated code writes maps 8-12% faster in Node (GitHub's EPYC 7763, 9V45, 9V74 and Neoverse-N2 runners).
- Go:
boolarrays are checked and copied eight bytes at a time: writing 1,000 bools is 88-91% faster and reading them 56-69% faster (EPYC 7763, EPYC 9V74 and Neoverse-N2 on GitHub's ubuntu-26.04 and ubuntu-26.04-arm runners). - Go: arrays of fixed-size structs whose memory is their wire bytes (such as a
Vec2of two floats) are copied as one block: writing 1,000 is 90-93% faster and reading them 60-66% faster (EPYC 7763, EPYC 9V74 and Neoverse-N2 on GitHub's ubuntu-26.04 and ubuntu-26.04-arm runners). - Go: arrays of
DateTime,decimal,Guidand 128-bit integers are read and written as one block: an object with aDateTime[](the .NET benchmark's SimpleClass) serializes 3.0-5.0% and deserializes 1.5-2.9% faster (EPYC 7763, EPYC 9V74 and Neoverse-N2 on GitHub's ubuntu-26.04 and ubuntu-26.04-arm runners). - Go: struct tags read and write slices of bools, dates, decimals and Guids as one block: SimpleClass through tags serializes 2.4-6.2% and deserializes 6.6-7.6% faster, with one allocation fewer (EPYC 7763, EPYC 9V74 and Neoverse-N2 on GitHub's ubuntu-26.04 and ubuntu-26.04-arm runners).
- Go: sets and dictionaries of integer keys are checked for repeated keys with a sort instead of a map: reading a
HashSet<int>of 1,000 is 51-63% faster (EPYC 7763, 9V45, 9V74, Xeon 6973P and Neoverse-N2 on GitHub's ubuntu-26.04 and ubuntu-26.04-arm runners). - Go: struct tags read a
[]stringof short ASCII strings 7.5-18.5% faster, and[NinoUtf8]strings are written 7.8-15.0% faster (1,000 strings; EPYC 7763, 9V45, 9V74, Xeon Platinum 8573C and Neoverse-N2 on GitHub's ubuntu-26.04 and ubuntu-26.04-arm runners). - Go: on Arm64, writing a
[]stringof short strings is 8.3-8.4% faster (Neoverse-N2), and[NinoUtf8]strings are read 1.5-5.0% faster (EPYC 9V45, 9V74, Xeon Platinum 8573C and Neoverse-N2; GitHub's ubuntu-26.04 and ubuntu-26.04-arm runners). - Deserializing a new instance of a class with string members no longer keeps the reader in memory: SimpleClass 1.4-3.5% faster, version-tolerant TolerantClass 2.3-4.6%, a new instance of a pooled type 1.5-2.6% (AMD EPYC 7763 and Neoverse-N2).
- Classes with string members deserialize 13 to 18% faster on x64 (AMD EPYC 7763) and Arm64 (Neoverse-N2): SimpleClass and the pooled benchmark's new objects, whose string members are read without keeping the reader in memory (previous commit) and whose one-char strings are no longer allocated.
- SortedList<int, int> deserializes 21% faster on Arm64 (Neoverse-N2); SortedDictionary 3% faster, and ImmutableDictionary, ImmutableHashSet and SortedSet up to 3% faster, on x64 (AMD EPYC 7763) and Arm64.
- Dictionary<string, string> deserializes 9% faster on x64 (AMD EPYC 7763) and 3% faster on Arm64 (Neoverse-N2).
- Deserializing into existing objects (pooled or
reftargets) whose long [NinoUtf8] text has not changed is 17 to 22% faster on x64 (AMD EPYC 7763) and Arm64 (Neoverse-N2) in the pooled benchmark. - Lists of fixed-size values read and write as one block. Reading 10,000 elements on GitHub's runners (AMD EPYC 7763 / Arm Neoverse-N2):
bool3.4 µs to 0.38 µs (−89% / −79%),Guid−88% / −86%,DateOnly−91% / −87%,DateTime−47% / −46%,decimal−25% / −24%, a raw struct −73% / −78%,(i32, i32)−86% / −85%,(i64, bool)−48% / −42%. Writing:boolandi8−99% / −98%, a raw struct −75% / −80%,(i32, i32)−85% / −87%,(i64, bool)−80% / −76%,decimal−38% on EPYC 7763 (unchanged on Neoverse-N2). - Maps of fixed-size keys and values (numbers, value types, raw structs, tuples of them) serialize as one block in Rust: writing 1,000
(i32, i32)entries takes 44 to 65% less time on GitHub's runners (AMD EPYC 7763 −44%, EPYC 9V45 −48%, EPYC 9V74 −49%, Intel Xeon Platinum −51%, Arm Neoverse-N2 −65%). - Maps of fixed-size keys and values deserialize 1 to 6% faster in Rust on x64 (reading 1,000
(i32, i32)entries on GitHub's AMD EPYC 7763, 9V45 and 9V74 and Intel Xeon Platinum runners), and maps of 8-byte entries such as(i32, i32)0.8% faster on Arm64 (Neoverse-N2). - The Go runtime reads and writes maps of primitive keys and values as one block (
ReadPrimitiveMap,WritePrimitiveMap; struct tags too): SimpleClass serializes 5-11 % faster and deserializes 4-7 % faster with the generated-style codec, and 7-14 % / 2-6 % faster through struct tags (GitHub's AMD EPYC 7763, 9V45 and 9V74, Intel Xeon and Neoverse-N2 runners). - ConcurrentDictionary<int, int> deserializes 18 to 23% faster on x64 with AMD EPYC 7763 (on par on EPYC 9V45 and 9V74 and on Arm64).
- Rust: writing strings that are neither ASCII nor Latin-1 is about 3.5 % faster on x64 CPUs with AVX2 but without AVX-512 (1,000 mixed strings, EPYC 7763).
- On 32-bit .NET (x86, WebAssembly, Arm32) and 32-bit .NET Framework, values of 8 bytes or more are read and written with single unaligned loads and stores, and copies of up to 64 bytes no longer call
memmove; Mono and IL2CPP on 32-bit Arm keep the byte copies. No GitHub runner covers 32-bit, so this is not measured. - On .NET Framework and .NET Core 3.0 to 7 (the netstandard builds), long Latin-1 strings are narrowed and widened with
Vector<T>where the JIT accelerates it. On Mono, raw structs with padding no longer go throughVector<T>, which Mono 6.8 reports as accelerated but runs slower than the 8-byte path. Not measured on GitHub runners. - Rust: writing lists larger than the CPU's last-level cache whose elements hold strings, vectors or boxes is up to 4 times as fast (1,000,000 shuffled records: 3 to 4.6 times on EPYC and Neoverse-N2, 1.6 times on Xeon 8370C); cached lists are unchanged.
- On Mono with the System.Numerics.Vectors package (the netstandard builds outside Unity), arrays of raw structs with padding are masked with
Vector<byte>(SSE) again, faster than the 8-byte path. Not measured on GitHub runners. - Unity's Burst kernels are called through C# function pointers and run from 2 values for arrays and lists of tuples and structs (was 16) and from 16 chars for Latin-1 strings (was 32): writing 2 to 12 small tuples into a buffer is 1.2-3.4x and strings of 16 to 31 chars 1.2-1.7x as fast (IL2CPP, Linux x64 GitHub runner, Unity 2022.3 and 6000.3).
- Unity WebGL players with WebAssembly SIMD write and read 1000-char Latin-1 strings 2.1-6x and write padded struct arrays of 1000 values 2.3-4.2x as fast (headless Chrome, Linux x64 GitHub runner).
- Rust writes strings with non-Latin-1 text faster on AVX-512 CPUs: 1,000 mixed strings serialize in 6-9 % less time on GitHub's AMD EPYC 9V45 and Intel Xeon 8573C and 8370C runners, 3.5 % on EPYC 9V74.
- Arrays of padded structs with a bool (1,000
Waypoints: two floats, an(int, short)and a bool) deserialize in 0.80 to 0.92 times the time (1,522 to 1,210 ns on EPYC 9V74, 1,874 to 1,718 ns on EPYC 7763, 2,553 to 2,288 ns on Neoverse-N2), and serialize 3.4 times as fast on Arm64 (1,438 to 427 ns on Neoverse-N2; GitHub runners). - Large arrays of raw values (10,000
Vector4, 160 KB) serialize 9.5% faster on Intel Xeon 8370C (3.51 to 3.18 µs) and deserialize 4.5-7.7% faster on AMD EPYC 9V45 and 9V74 (96.6 to 92.2 µs, 111.0 to 102.4 µs; GitHub runners). - Arrays of padded structs over 16 bytes (1,000
Waypoints: two floats, an(int, short)and a bool) serialize in 0.65 times the time on Intel Xeon 8573C (386 to 252 ns), 0.76 on AMD EPYC 9V45 (210 to 159 ns) and 0.97 on EPYC 9V74 (GitHub runners). - With Burst, the ASCII text of
[NinoUtf8]strings is written 1.5 to 1.6 times as fast from 16 chars and 1.9 to 2.1 times at 1000, and read 1.06 to 1.24 times as fast from 16 chars and 1.2 to 1.25 times from 1000 (IL2CPP, Linux x64 GitHub runners, AMD EPYC 7763 and 9V74, Unity 2022.3 and 6000.3); WebGL players with SIMD write it 1.2 to 1.9 and read it 1.2 to 1.7 times as fast (headless Chrome). CJK text is on par. - Members of generic
[NinoType]s and of[NinoType]s generated in another assembly are called directly instead of through a virtual call: a generic type holding an unmanaged tuple deserializes in 0.86-0.94 of the time on GitHub's AMD EPYC 7763, 9V45 and 9V74 and Neoverse-N2 runners and serializes 2-4% faster on the 7763, 9V74 and Neoverse-N2 (noise floor 0.99-1.01). HashSet<T>andDictionary<TKey, TValue>of unmanaged values serialize their entries several at a time with SIMD shuffles: aHashSet<int>of 1,000 in 0.34-0.49 times the time (327 to 141 ns on EPYC 7763, 372 to 126 ns on Neoverse-N2), aDictionary<int, int>of 1,000 in 0.29-0.56 times (430 to 212 ns on EPYC 7763, 369 to 107 ns on Xeon Platinum 8573C), and the polymorphic class array, whose members hold such collections, in 0.69-0.84 times (GitHub's EPYC 7763, 9V45, 9V74, Xeon Platinum 8573C and 8370C, Xeon 6973P and Neoverse-N2 runners).- In
NinoSettings.Untrustedmode, the values checked in arrays and lists of raw structs are checked without a branch per value, and with SIMD on .NET 8+: a list of 1,000 structs with aDateTimeand abooldeserializes in 0.47-0.69 times the time (5.55 to 2.76 µs on EPYC 7763, 6.13 to 2.86 µs on Neoverse-N2; GitHub runners). - On Unity and .NET Framework (the netstandard builds), reading a Latin-1 or ASCII string into an instance that holds one of the same length compares them eight bytes at a time instead of one char at a time; no GitHub runner covers these platforms, so there are no numbers.
- Rust lists of plain structs whose members are all fixed-size (
#[derive(NinoRead, NinoWrite)]or nino-gen) are read and written as one block: 10,000{ i32, f32 }read 80-91% and write 85-88% faster, 10,000{ i64, bool, i32 }read 61-78% and write 63-88% faster on GitHub's AMD EPYC 7763, 9V45, 9V74, Intel Xeon 8573C, 8370C and Neoverse-N2/V3 runners. - Go code nino-gen generates calls each element of a collection directly instead of through a function value: on GitHub's runners, a class with a dictionary and lists serializes 3-5% faster (AMD EPYC 9V45 and 9V74, Intel Xeon 8370C and 6973P-C, Neoverse-N2 and V3) and a list of 1,000,000 records reads up to 8% faster (EPYC 7763).
- Go
ReadBlock/WriteBlockofDateTime,TimeOnlyanddecimal, andReadRaw/WriteRawof fixed-size structs with abool, check their values four at a time: writing 1,000 values is 29-38% faster forDateTime, 2-25% fordecimal, 9-24% forTimeOnlyand 9-39% for an 8-byte struct with a bool, and reading them 3-14% faster (GitHub's AMD EPYC 7763, 9V45 and 9V74, Intel Xeon Platinum 8573C and 8370C, Neoverse-N2). - Go
Reader.UTF16reads 1,000 wide code units 60-63% faster andWriter.UTF16writes them 92-96% faster; Latin-1 code units are written 40-64% faster (GitHub's AMD EPYC 7763 and 9V74, Intel Xeon Platinum 8370C, Neoverse-N2). - With
NinoPrefetchLargeCollections, writing a million shuffled objects is 2.0-4.8x as fast and reading into them 2.0-3.6x (GitHub runners, EPYC 7763/9V45/9V74 and Neoverse-N2); short top-level collections can be up to 6 % slower. - Generated Go calls class bodies directly: on GitHub's runners a list of 1,000,000 records reads 4-7% faster (AMD EPYC 7763, 9V45, 9V74, Neoverse-N2) and writes 2-6% faster than before the collection change (EPYC, Xeon 8573C, Neoverse-N2).
- 1,000 padded 24-byte structs with a bool (the benchmarks'
Waypoint) deserialize in 0.77 to 0.85 times the time on GitHub's AMD EPYC 7763 and 9V74, Intel Xeon Platinum 8370C and Neoverse-N2 runners (EPYC 9V74: 1,203 to 922 ns), and a list of 1,000(int, long)in 0.97 to 0.99 (A/B run 40935400095). - Arrays of padded raw structs serialize about 1.4 times as fast on Intel Ice Lake (GitHub's Xeon Platinum 8370C: 1,000 8-byte structs with a padding byte in 83 ns instead of 116, MemoryPack 88); other CPUs are unchanged.
- TypeScript deserialize reads a struct member into the default instance its class already holds instead of making a second one: Mesh (arrays of Vec3 structs) deserializes 8 to 24 % faster on GitHub runners (A/B 40969410249), other cases on par.
- In Unity's IL2CPP players, arrays and lists of tuples and fixed-size structs no longer allocate for each element when no Burst kernel handles them, and top-level
Serializecalls no longer allocate before writing (the netstandard builds box each formatter once). - Members of type
Tin generic[NinoType]s are direct calls on every runtime (IL2CPP, Mono and Native AOT made a virtual call per value): on .NET, generic polymorphic arrays read and write on par or faster on GitHub's x64 and Arm64 runners (serialize up to 11% faster on AMD EPYC 9V74), except an array of a generic base over a(class, int)tuple, which deserializes 4-5% slower on Xeon 8573C (Emerald Rapids). - Go: sets read from the data are checked for a repeated element in a seeded table instead of a sort or a map, and sets of numbers are read as one block: a
HashSet<int>of 1,000 reads in 0.16-0.19 of the time and aHashSet<string>of 1,000 in 0.54-0.66 (GitHub's AMD EPYC and Neoverse-N2 runners). Regenerate Go code to take it. - Rust: a sealed class's generated read and write methods are
#[inline], as the derive's were: a list of 1,000 records reads 2.0-4.3% and writes 2.0-9.2% faster (GitHub's AMD EPYC and Neoverse-N2 runners). Regenerate Rust code to take it. - Arrays of padded structs serialize 1.2 times as fast on Intel Xeon Platinum 8573C (Emerald Rapids), whose cloud VMs hide AVX512-FP16: 1,000
Waypoints in 259 ns instead of 336 and 1,000 8-byte structs with a padding byte in 65 ns instead of 83 (GitHub runners); Ice Lake keeps its 32-byte kernels. - Arrays of padded structs with a
bool(such asWaypoint) deserialize 4-8% faster on x64 CPUs without AVX-512, the bools checked in the same pass (GitHub's AMD EPYC 7763: 1,000Waypoints in 1,240 ns instead of 1,360). - On Intel Ice Lake (Xeon Platinum 8370C), lists and arrays of padded fixed-size values such as
(int, long)andDictionary<int, int>entries are written from cache-line-aligned loads: 1,000(int, long)serialize in 0.92 of the time (235 to 221 ns) and a 1,000-entryDictionary<int, int>in 0.81 (250 to 200 ns). Other CPUs are unchanged. - On Intel Ice Lake under Linux (Xeon Platinum 8370C), raw blocks of 2 to 32 KB (unpadded structs,
KeyValuePair<int, int>, primitives) are copied with the C library'srep movsb: 1,000KeyValuePair<int, int>serialize in 0.85 of the time (151 to 128 ns) and a 1,000-intQueue<int>in 0.78 (94 to 74 ns). Other CPUs are unchanged. - Rust on Arm64 writes short strings that are not ASCII faster: 1,000 mixed strings serialize in 7.7 % less time on GitHub's Neoverse-N2 runners, and lists of 16- to 24-char Latin-1, Cyrillic, CJK and mixed strings in 4-13 % less.
- Collections that hold themselves without a
[NinoType]body (class Tree : List<Tree>,class Map : Dictionary<string, Map>) call each level directly instead of through a virtual call: a 5,461-list tree serializes in 0.35-0.42x and deserializes in 0.52-0.58x the time on x64 and Arm64 runners. - Reading a CultureInfo no longer asks ICU each time for a name it already accepted: 0.43-0.57 of v4.0.0-rc.7's time and 0.05 of the previous build's on every runner CPU (.NET A/B runs 41455371503, 41534216283).
- A TimeZoneInfo of a system zone reads its null rules inline: 0.83-0.92 of the previous build's time, 0.89-0.98 of v4.0.0-rc.7's (.NET A/B run 41534216283).
- Strings read again into existing objects (pooled or reused instances) are compared without a second call: 1,000,000 shuffled items read into existing objects in 0.53-0.86 of the previous build's time and 0.56-0.95 of v4.0.0-rc.7's (.NET A/B runs 41534216283, 41576130603).
- Reading into existing objects (pooled or reused instances) no longer stores back a string member that did not change: NinoDeserializePooled in 0.86-0.97 of v4.0.0-rc.7's time on Neoverse-N2 and Xeon 6975P and 8573C (.NET A/B run 41621699299), 0.89-0.93 of the previous build's on every CPU.
Fixes
- TypeScript reads and writes values nested deeper than 128 class bodies (linked lists, trees) byte for byte as .NET does, and deeply nested or forged data throws a NinoError instead of overflowing the stack.
- Reading a
[NinoUtf8]string whose bytes are not UTF-8 in TypeScript throws aNinoErrorinstead of aTypeError. - Deserializing a Unity
Gradientfrom data with -1 as its alpha key count throws Nino'sInvalidOperationExceptioninstead ofOverflowException. - With Nino.AspNetCore, a collectible
AssemblyLoadContextwhose types went through the MVC formatters can unload again. - Editing an
[assembly: NinoFormatter(...)]registration in the IDE now updates the generated formatters that use it, instead of keeping the old ones until the next rebuild. - Top-level
NinoSerializer/NinoDeserializercalls written through ausingalias orusing staticare now intercepted and their formatters registered for AOT, like calls that name the class. - The source generator no longer keeps the cached models and syntax trees of closed projects in memory, which grew the IDE's (and build servers') memory over a long session.
- The published layouts (
NinoTypeLayoutAttribute) ofHalf,Rune,DateOnly,TimeOnly,Index,Range,Complex,Int128,UInt128and arrays of nullable values now show the bytes Nino writes instead of the placeholders of reference assemblies. - Members (and elements) that are tuples of eight items or more are now serialized instead of being skipped with warning NINO007.
- Projects with two
[NinoType]s whose names differ only by case (RawSandRaws) get their generated code again; the generator failed with CS8785 and added none. - An assembly removed with
NinoRegistry.Remove(or by its collectibleAssemblyLoadContextunloading) is no longer registered again by a later read of an unknown type id, which kept it from unloading; an assembly loaded right after another one unloaded is now found by reads by type id. NinoSerializer.SerializeBoxed(null, type)for a value type throwsArgumentNullExceptioninstead of aNullReferenceException.- On a big-endian host, raw values, arrays of them and fixed-size types serialized through the top-level calls are rejected like every other type, instead of being written in the host's byte order.
- Reading data that nests a collection type holding itself (
class Tree : List<Tree>) deeper than the depth limit throwsInvalidOperationExceptioninstead of overflowing the stack, and the runtime-built formatter of such a type no longer overflows the stack while it is created. - Reading the same unknown type id again (corrupt, hostile or stale data) no longer searches every registry and loaded assembly each time; the id is found as soon as an assembly declaring it is loaded or registered.
- Under NativeAOT, generic unmanaged structs that are not
[NinoType]s serialize as raw values, as they do with the JIT and in generated code, instead of failing for lack of a formatter. NinoSubtypeHandlercan no longer be subclassed, norNinoPolymorphic<TBase>.Register(NinoSubtypeHandler)called, outside Nino.Core: only generated registrations add polymorphic subtypes.- Generated TypeScript for a
[NinoType]without members compiles withnoUnusedParameterson (its empty read and write bodies name their parameters_r,_wand_v). - Reading data Nino never writes now throws
NinoDataExceptionin every mode: aboolor null/presence byte other than 0 or 1, a keyed member whose entry holds more bytes than its value, and a version-tolerant deferred section whose offset or entries do not match its region. Bools inside raw structs and tuples are still copied as they are. - Raw keys read into an existing
Dictionary<TKey, TValue>with a custom comparer are now hashed by that comparer; before, lookups could miss them. - Serializing an object graph nested deeper than
NinoSettings.MaxDepththrough structs or collections that hold themselves (or one that contains itself) now throwsInvalidOperationExceptioninstead of overflowing the stack. - The TypeScript runtime now rejects data .NET rejects: a repeated key or element in a map or set (of numbers, strings, bigints, booleans or null), a keyed member whose value leaves bytes in its entry, and a malformed section of deferred bodies, each with a
NinoError. - The Go and Rust readers accept a
DateTimethat .NET wrote for a local time in the repeated hour of a daylight saving change (kind 3), instead of failing with invalid data. - A closed generic
[NinoType]struct was written in two encodings, raw inside other generated types and member by member through a generic type or a top-level call, so one could not read what the other wrote. - Value tuples of eight items or more can be the type argument of a generic
[NinoType]that generated code never sees closed; Nino.Core threw "no formatter" for them. - Writing an object graph that nests structs holding themselves around records (or other classes Nino cannot defer) now throws
InvalidOperationExceptiononce the two together passNinoSettings.MaxDepth, instead of writing data its own reader rejects. - Reading a
ConcurrentDictionarynow enforcesNinoSettings.MaxHashCollisionslike the other hash tables: keys built to share a hash bucket throwNinoDataExceptioninstead of making the read take quadratic time. - A closed generic subtype held in a slot of a closed generic type (
ListShelf<string>in aShelf<string>member) is now read in a process that never wrote or named it, instead of failing as an unregistered subtype. - Reading a
[NinoType]collection whose element count marks it null throwsNinoDataExceptioninstead ofNullReferenceException. - On Intel Macs with AVX-512 (macOS 12.2 and later), the Go runtime's string writer and reader now take their AVX-512 kernels; they took the AVX2 ones.
- ASP.NET Core answers 400 Bad Request only for request bodies that are not valid Nino data (
NinoDataException); a failure on the server's side while reading one, such as a[NinoFactory]that returns null, is no longer reported as the client's fault. - Unity's
RectOffsetnow rejects a presence byte other than 0 or 1 as invalid data (NinoDataException), as every other nullable value does, instead of reading it as present. - The
nino-serializationnpm package exports its version asninoVersion, instead of the placeholder"__NINO_VERSION__". - The
nino-serializationnpm package declares the Node versions it runs on (engines: 15 and later), instead of 18. - The TypeScript runtime rejects a
decimalwhose scale exceeds 28 or whose flags have other bits set, as the Go and Rust runtimes do, instead of reading a value it cannot write back. - The TypeScript runtime rejects a
DateTimewhose ticks exceedDateTime.MaxValue's, as the Go and Rust runtimes do, instead of reading a date it cannot write back. - Under NativeAOT on x64, arrays and lists of value tuples,
KeyValuePairs and fixed-size structs whose memory differs from their bytes (such as(int, long)) are moved by the SSSE3 shuffle instead of its software fallback, which in a local run made writing 1,000(int, long)about 13 times as slow (25 us against 1.9 us with the fix) and reading them about 7 times as slow. The AVX2 and AVX-512 paths still needIlcInstructionSetthere. - Rust:
#[derive(NinoRead)]no longer rejects a list of structs with a#[nino(utf8)]member holding short strings (InvalidLength). - NINO007 now also reports a member whose type closes a generic
[NinoType]over a type Nino cannot write (Box<Action>, also inside collections, arrays and other generics), where writing any value threw at run time; the bytes do not change. - Reading invalid data into an existing value (
Deserialize(data, ref value)) no longer leaves it holding a new array of uninitialized memory when the data was rejected after the array was allocated: rawT?arrays and lists,boollists, collections of block structs with aboolsuch as(int, bool)[], and checked values inUntrustedmode. - Go: sets and dictionaries read from the data reject a second NaN element or key, as .NET does; they accepted it.
- TypeScript: a map or set of more than 128 number or bigint keys read from untrusted data can no longer be made quadratic by keys chosen to share one of V8's hash buckets; such data throws a
NinoError, as .NET'sMaxHashCollisionsdoes. - TypeScript: a map or set of more than 128 string keys read from untrusted data can no longer be made quadratic by keys that are array indices ("0", "2048", ...), which V8 hashes by their value without a seed; such data throws a
NinoError, as number and bigint keys already did. - On .NET 8 and later, a
CultureInforead from untrusted data can no longer grow the process's memory without bound (.NET keeps every culture it creates): only cultures the runtime ships read back, including aliases such aszh-CN; custom cultures and names with-u-or-x-extensions are now invalid data (NinoDataException), and in invariant globalization mode only the invariant culture reads back. .NET Framework, Mono and Unity are unchanged. - Reading a Unity
Gradientwhose data claims more than 8 color or alpha keys now throwsNinoDataExceptionbefore allocating, instead of an exception from Unity's setters. - Under Native AOT, a closed generic
[NinoType]named only inNinoSerializer.GetFixedSize<T>()or as thetypeofargument ofSerializeBoxed,DeserializeBoxedorIsSupportedworks as on the JIT; the generator registered only the type arguments ofSerializeandDeserialize, and such calls threw "no formatter". - On 32-bit x86 .NET, a struct written as raw bytes that holds a
DateTimeafter a smaller field (such asstruct { int A; DateTime B; }) is converted to the canonical layout instead of being copied as its 12-byte x86 memory, which other runtimes could not read. - On .NET Framework, a struct written as raw bytes that holds a
DateTimeafter a smaller field and before another (such asstruct { byte A; DateTime B; int C; }) is written in the canonical layout instead of its reordered memory, which other runtimes could not read and which the 32-bit build could not read back.
Every change between v4 releases: Changelog.
What's Changed since v4.0.0-rc.7
806 commits
⚠️ Breaking changes
- refactor(core)!: rename the body trait flags NinoBody to NinoBodyTraits (172ca3a) by @JasonXuDeveloper
- refactor(go)!: keyed members are Members, set and key checks are CheckUnique* (d904236) by @JasonXuDeveloper
- refactor(codegen)!: --numeric-arrays plain|typed (ad17f51) by @JasonXuDeveloper
- refactor(core)!: block formatters are fixed-size formatters (619d2d4) by @JasonXuDeveloper
- refactor!: rename the v3-era and unclear public names before 4.0 (31313cb) by @JasonXuDeveloper
- refactor(go)!: remove the collection helpers that take each element's code as a function value (62ccd3f) by @JasonXuDeveloper
- refactor(rust)!: INLINE_DEPTH and MAX_DEPTH (83d37f5) by @JasonXuDeveloper
- refactor(typescript)!: NinoReader and NinoWriter beginRegion and exitBody (8d373cb) by @JasonXuDeveloper
- refactor(runtimes)!: clear names for the runtimes' generated-code API, no aliases before 4.0 (084a463) by @JasonXuDeveloper
- refactor(core)!: the generated code's subtype registry is NinoSubtypes<TBase> (c3c60c5) by @JasonXuDeveloper
- fix(rust)!: free each read's tracked objects with it, cycles included (b5aaf06) by @JasonXuDeveloper
✨ Features (53)
- feat(go): add a Go runtime for Nino's wire format (e287a0b) by @JasonXuDeveloper
- feat(go): read keyed entries strictly and check sections and value ranges (d677499) by @JasonXuDeveloper
- feat(go): map Go structs to Nino types with struct tags (7030df5) by @JasonXuDeveloper
- feat(go): MarshalAs for interface roots, and fixes found by the corpus (713dc56) by @JasonXuDeveloper
- feat(go): struct tags and layouts on the sample types, and the corpus through them (604d56a) by @JasonXuDeveloper
- feat(go): benchmark the struct-tag path, and write maps without a value per key (5b1d153) by @JasonXuDeveloper
- feat(rust): add a Rust runtime for Nino's wire format (c692776) by @JasonXuDeveloper
- feat(rust): reject deferred-body sections that do not match their bodies (2fc9c70) by @JasonXuDeveloper
- feat(rust): bound up-front collection allocation per read, not per collection (4804148) by @JasonXuDeveloper
- feat(rust): serde for Nino's value types (9ea2ffb) by @JasonXuDeveloper
- feat(rust): #[derive(NinoRead, NinoWrite)] (423631e) by @JasonXuDeveloper
- feat(analyzer): warn about [NinoFormerName] on a generic type used polymorphically (NINO027) (f7e956d) by @JasonXuDeveloper
- feat(generator): publish whether a type's body can be deferred, where Nino.Core can hold it (ca7f0ea) by @JasonXuDeveloper
- feat(generator): say where each intercepted call is in the Roslyn 4.12 interceptors (c740574) by @JasonXuDeveloper
- feat(core): publish whether a type is deferrable in its layout (f08485e) by @JasonXuDeveloper
- feat(runtimes): take deferrability from the published layout in Go and Rust (f2fd1cc) by @JasonXuDeveloper
- feat(codegen): Go target (131b46c) by @JasonXuDeveloper
- feat(codegen): Rust target (c474523) by @JasonXuDeveloper
- feat(codegen): nino-gen command line and package (7b8cda6) by @JasonXuDeveloper
- feat(core): throw NinoDataException for invalid data (eb9c4a5) by @JasonXuDeveloper
- feat(core): NinoSettings.MaxDepth and MaxCollectionLength (bfcb52f) by @JasonXuDeveloper
- feat(collections): bound hash collisions per key (NinoSettings.MaxHashCollisions) (3ecfe62) by @JasonXuDeveloper
- feat(core): NinoSettings.Untrusted, the checks that cost every read (51e74f9) by @JasonXuDeveloper
- feat(generator): reach non-public members of generic types through [UnsafeAccessor] on .NET 9+ (bb08738) by @JasonXuDeveloper
- feat(generator): read and write fixed-size structs that are not raw as blocks (1c4071c) by @JasonXuDeveloper
- feat(codegen): Nino.Codegen runs nino-gen in builds for TypeScript, Go and Rust (2f9d76c) by @JasonXuDeveloper
- feat(codegen): generate generic types, and a --maps option for Rust (a6870e3) by @JasonXuDeveloper
- feat(codegen): closed generic types in polymorphic and object slots (c4b23a1) by @JasonXuDeveloper
- feat(core): an optional kernel hook for the netstandard builds (a76311a) by @JasonXuDeveloper
- feat(unity): Burst-compiled kernels for blocks and Latin-1 text (7ff9830) by @JasonXuDeveloper
- feat(runtimes): reference tracking in the TypeScript, Go and Rust runtimes (d146bba) by @JasonXuDeveloper
- feat(codegen): generic slots, reference tracking and [NinoType] collections in nino-gen (806d1c4) by @JasonXuDeveloper
- feat(typescript): deserializeExact, which rejects bytes after the value (8b30a84) by @JasonXuDeveloper
- feat(core): one, three or five byte string headers (10e464b) by @JasonXuDeveloper
- feat(typescript): one, three or five byte string headers (69463eb) by @JasonXuDeveloper
- feat(go): one, three or five byte string headers (94f6ac8) by @JasonXuDeveloper
- feat(rust): one, three or five byte string headers (db2c5d5) by @JasonXuDeveloper
- feat(unity): WebAssembly SIMD kernels for WebGL players (3d562f2) by @JasonXuDeveloper
- feat(core): hold native kernels as function pointers, with ASCII kernels for UTF-8 (569a436) by @JasonXuDeveloper
- feat(unity): Burst and WebGL kernels for [NinoUtf8] strings, registered as function pointers (b8c6986) by @JasonXuDeveloper
- feat(codegen): typed arrays for numbers in TypeScript with --numeric-arrays typed (fa74014) by @JasonXuDeveloper
- feat(typescript): serializeInto a reused NinoWriter or a Uint8Array (224e92f) by @JasonXuDeveloper
- feat(generator): opt-in prefetch of long top-level collections of scattered objects (4b2863d) by @JasonXuDeveloper
- feat(go): class bodies and dictionary key checks without a call through a function value (414a0a2) by @JasonXuDeveloper
- feat(go): check a set's elements once with UniqueValues, read by a loop with direct calls (3bcf28c) by @JasonXuDeveloper
- feat(rust): deserialize_into reads into an existing value (9c57aa2) by @JasonXuDeveloper
- feat(typescript): deserializeInto reads into an existing value (547eaff) by @JasonXuDeveloper
- feat(go): DeserializeInto reads into an existing value (f5903ec) by @JasonXuDeveloper
- feat(analyzer): code fixes for NINO024 and NINO025; older messages say what happens (8408003) by @JasonXuDeveloper
- feat(analyzer): NINO028 for a [NinoMember] on a member that is never serialized (0986175) by @JasonXuDeveloper
- feat(analyzer): NINO029 on Nino.Core formatters and Writer/Reader methods used by hand (5c66184) by @JasonXuDeveloper
- feat(rust): nullables inside raw structs as Option<T> (7b78fca) by @JasonXuDeveloper
- feat(analyzer): NINO029 on fields, properties and locals of Nino.Core formatters over Vector<T> (f56a1bb) by @JasonXuDeveloper
⚡ Performance (148)
- perf(immutable): pass the writer by value to the out-of-line fallbacks (6441057) by @JasonXuDeveloper
- perf(aspnetcore): read a body already in the pipe without an async method (0c9423c) by @JasonXuDeveloper
- perf(typescript): write arrays with an index loop instead of an iterator (98d6c63) by @JasonXuDeveloper
- perf(go): check repeated keys with one hash operation (c97daa1) by @JasonXuDeveloper
- perf(generator): share the facts of files whose preprocessor symbols differ from the project's (e2cc33f) by @JasonXuDeveloper
- perf(generator): alias a formatter's own type in its file (fa13fea) by @JasonXuDeveloper
- perf(generator): reuse the formatter files of models that did not change (2c30ac4) by @JasonXuDeveloper
- perf(generator): cache top-level calls and read the compilation in one step (4fbbc24) by @JasonXuDeveloper
- perf(immutable): skip zeroing locals, as Nino.Core does (b293049) by @JasonXuDeveloper
- perf(core): bump cursors in the one-shuffle-per-value block loops (da33878) by @JasonXuDeveloper
- perf(core): end a tracked graph's outer regions without a second writer (9a26995) by @JasonXuDeveloper
- perf(core): write values of more than 16 bytes in lane groups with AVX-512 VBMI (ac6aa9b) by @JasonXuDeveloper
- perf(core): bump cursors in the AVX-512 group loops (ac062da) by @JasonXuDeveloper
- perf(core): open a tracked object's slot inline when reading (2a0a591) by @JasonXuDeveloper
- perf(rust): write strings in one pass over their UTF-8 (58e7785) by @JasonXuDeveloper
- perf(go): write and read non-ASCII strings in one pass (a94a456) by @JasonXuDeveloper
- perf(core): keep the reader's and writer's helpers inline under the JIT's budget (2617303) by @JasonXuDeveloper
- perf(generator): mark generated entry points and forwarders AggressiveInlining (d8fe0ff) by @JasonXuDeveloper
- perf(go): convert strings a word at a time, without reading text twice (4838d7e) by @JasonXuDeveloper
- perf(rust): keep class bodies and string reads inline, deep paths out of line (78d42a4) by @JasonXuDeveloper
- perf(core): read a time zone's rules out of line so the reader stays in registers (afcff03) by @JasonXuDeveloper
- perf(rust): transcode written strings a block at a time in one unsafe module (db6d29d) by @JasonXuDeveloper
- perf(go): write strings through unsafe pointers into reserved room (acd534b) by @JasonXuDeveloper
- perf(go): copy and widen long ASCII runs with SSE2 and NEON (5c37a56) by @JasonXuDeveloper
- perf(typescript): deserialize small values from a reused reader (5cac5a8) by @JasonXuDeveloper
- perf(rust): copy short ASCII strings with their header in a few stores (a83a1bc) by @JasonXuDeveloper
- perf(rust): build short ASCII strings without str::from_utf8 (a4b4150) by @JasonXuDeveloper
- perf(rust): derived raw structs skip the padding check when they have none (821ab4d) by @JasonXuDeveloper
- perf(rust): transcode non-ASCII strings of up to 64 bytes in one pass (d08efb4) by @JasonXuDeveloper
- perf(go): read short strings into a block the reader keeps (324aa0c) by @JasonXuDeveloper
- perf(go): point StringPtr and Utf8Ptr into a block the reader keeps (ee1ff4e) by @JasonXuDeveloper
- perf(go): write Latin-1 text a char at a time, ASCII runs a word at a time (a1346c6) by @JasonXuDeveloper
- perf(typescript): start serialize's buffers sized by the last value (ac98e2c) by @JasonXuDeveloper
- perf(typescript): write int32 lengths and string headers as four bytes (b57e3a6) by @JasonXuDeveloper
- perf(typescript): read UTF-16 text of up to 24 code units in one fromCharCode call (798ad64) by @JasonXuDeveloper
- perf(rust): one-pass SSE4.1 string writer, and decode read text straight into a String (842a506) by @JasonXuDeveloper
- perf(rust): read a boxed class in place on the heap (2d802f8) by @JasonXuDeveloper
- perf(go): transcode strings with AVX2 and AVX-512 (ae58e38) by @JasonXuDeveloper
- perf(generator): write Go string lists with WriteStringPtrs (6371965) by @JasonXuDeveloper
- perf(typescript): read and write longer text through WebAssembly in browsers (0aa0d83) by @JasonXuDeveloper
- perf(go): string kernels for NEON and SSSE3, one tier per instruction set (9e45794) by @JasonXuDeveloper
- perf(go): plain loop in WriteStrings where no vector writer is taken (557cc28) by @JasonXuDeveloper
- perf(unity): AVX2 permute in the Burst kernels for blocks of 256 values or more (2b67ce9) by @JasonXuDeveloper
- perf(go): write and read [NinoUtf8] strings in one pass (9532bcd) by @JasonXuDeveloper
- perf(typescript): narrow text of up to 48 chars in JavaScript with Node's methods (7cc7fee) by @JasonXuDeveloper
- perf(rust): string kernels per instruction set, for the writer and the reader (e6d217a) by @JasonXuDeveloper
- perf(rust): [NinoUtf8] strings with one reservation and a word-wise UTF-16 count (490c3ab) by @JasonXuDeveloper
- perf(typescript): read short UTF-16 code units inline, and write short wide text in JavaScript in Node (36cfdbf) by @JasonXuDeveloper
- perf(go): list kernels store short ASCII text and its header as one block (5fb431a) by @JasonXuDeveloper
- perf(go): read short ASCII strings as two words, and write a string's header with its first bytes (4798b2c) by @JasonXuDeveloper
- perf(go): struct tags read and write a []string as generated code does (ca50375) by @JasonXuDeveloper
- perf(go): hand short non-ASCII text of an AVX2 list kernel to its long path (18dceb5) by @JasonXuDeveloper
- perf(go): struct tags sort a map's keys read once (cc7bd7c) by @JasonXuDeveloper
- perf(go): write a short [NinoUtf8] string's headers with its first bytes (97a6daf) by @JasonXuDeveloper
- perf(go): struct tags read and write maps of built-in types as generated code does (d59a5fd) by @JasonXuDeveloper
- perf(go): write a [NinoUtf8] string's two three-byte headers as one store (9591290) by @JasonXuDeveloper
- perf(go): read a short [NinoUtf8] string's two counts as a pair (bf0df2b) by @JasonXuDeveloper
- perf(go): keep the amd64 list-of-strings loop's common case on one aligned straight path (c37e4a0) by @JasonXuDeveloper
- perf(go): keep the reader's text and string blocks by offset, not by reslicing (49a4b5b) by @JasonXuDeveloper
- perf(go): mask the last block of x64 text without a load that can cross a cache line (8e7d644) by @JasonXuDeveloper
- perf(go): keep the arm64 mask table's windows within one cache line (c094e2d) by @JasonXuDeveloper
- perf(go): read the AVX2 list kernel's short text from where it starts (feeab6b) by @JasonXuDeveloper
- perf(go): decode short Latin-1 text that is not ASCII without testing it again (2f1c454) by @JasonXuDeveloper
- perf(go): check UTF-8 text 16 bytes at a time on amd64 and arm64 (6f8e92f) by @JasonXuDeveloper
- perf(rust): store a short ASCII string's header with its first bytes (a67d437) by @JasonXuDeveloper
- perf(rust): read and write lists of strings in runs of short ASCII (5dfd3cf) by @JasonXuDeveloper
- perf(rust): write string headers with stores of a constant size, and a one-byte room for short text (9a45fe9) by @JasonXuDeveloper
- perf(rust): lists of mixed strings fall back to one string at a time (0adcac0) by @JasonXuDeveloper
- perf(rust): lists of strings leave the short ASCII run at the first string it does not take (0e15e81) by @JasonXuDeveloper
- perf(rust): inline the one-pass kernels' dispatch, and write and read a short [NinoUtf8] string's counts as a pair (7e52b80) by @JasonXuDeveloper
- perf(rust): give short text the most room its UTF-8 can take, without counting it first (a9e5132) by @JasonXuDeveloper
- perf(rust): read a [NinoUtf8] string of as many bytes as chars as ASCII (6f8c1f6) by @JasonXuDeveloper
- perf(rust): write the rest of a list of strings through a list loop per instruction set (d47adb0) by @JasonXuDeveloper
- perf(rust): return a string the reader decodes in two registers (7cc0994) by @JasonXuDeveloper
- perf(rust): place the loops over a list of strings at a 64-byte boundary (f7d60b3) by @JasonXuDeveloper
- perf(rust): write a block of fixed-size elements without zero-filling it first (5cd356b) by @JasonXuDeveloper
- perf(rust): write a collection of fixed-size elements with one capacity check (6ece123) by @JasonXuDeveloper
- perf(rust): test and store one 16 to 32-byte ASCII string in SSE2 registers on x64 (9b03940) by @JasonXuDeveloper
- perf(codegen): write a TypeScript map from its keys and values side by side (1d1df51) by @JasonXuDeveloper
- perf(typescript): read UTF-16 code units and long string headers with one DataView call each (03d3e31) by @JasonXuDeveloper
- perf(typescript): read short Latin-1 text four bytes a DataView call (43e023b) by @JasonXuDeveloper
- perf(typescript): write short one-byte text four chars a DataView store (09bd47d) by @JasonXuDeveloper
- perf(typescript): read a short one-byte string's header and text with one bounds check (faf2284) by @JasonXuDeveloper
- perf(typescript): read short Latin-1 text a byte a load again (cf198c0) by @JasonXuDeveloper
- perf(typescript): test the first char of four before reading the rest when writing short text (8d41b2b) by @JasonXuDeveloper
- perf(typescript): keep the reads of other strings out of what V8 inlines into string() (5e6aee9) by @JasonXuDeveloper
- perf(go): check and copy a bool array eight bytes at a time (73a5324) by @JasonXuDeveloper
- perf(go): copy arrays of raw structs whose Go memory is their bytes as one block (8744454) by @JasonXuDeveloper
- perf(go): read and write arrays of dates, decimals, Guids and 128-bit integers as one block (15c084f) by @JasonXuDeveloper
- perf(go): check the room for each char of Latin-1 and UTF-16 text decoded to UTF-8 once (fdf35df) by @JasonXuDeveloper
- perf(go): struct tags read and write slices of bools, dates, decimals and Guids as one block (e15accb) by @JasonXuDeveloper
- perf(go): check integer keys of sets and dictionaries for repeats with a sort, not a map (9f3c54b) by @JasonXuDeveloper
- perf(go): read and write dictionaries of primitive keys and values as one block (a6b3bd0) by @JasonXuDeveloper
- perf(go): copy arrays of raw structs with padding as one block, then clear the padding (25b028d) by @JasonXuDeveloper
- perf(go): read struct tags' string slices through ReadStrings (1a3711b) by @JasonXuDeveloper
- perf(go): read short ASCII in ReadStrings' loop and test [NinoUtf8] text for ASCII in place (f306c5e) by @JasonXuDeveloper
- perf(go): fewer calls a [NinoUtf8] string read, fewer instructions a string in the arm64 list kernel (3d40d63) by @JasonXuDeveloper
- perf(generator): read the strings of a new instance without their current value (f6fece5) by @JasonXuDeveloper
- perf(core): read a string member of an existing object without the reader's address (fcc3be7) by @JasonXuDeveloper
- perf(core): share the strings of one Latin-1 char (8992b29) by @JasonXuDeveloper
- perf(collections): read sorted and immutable collections without bounds checks (d935ad1) by @JasonXuDeveloper
- perf(collections): read Dictionary<string, string> pairs in a loop of their own (1163dde) by @JasonXuDeveloper
- perf(core): keep long unchanged UTF-8 text when reading into an existing object (b2cd50c) by @JasonXuDeveloper
- perf(rust): read and write lists of fixed-size elements as blocks (95e8f0d) by @JasonXuDeveloper
- perf(rust): write maps of fixed-size keys and values as one block (e159b0a) by @JasonXuDeveloper
- perf(rust): read maps of fixed-size keys and values with one bounds check an entry (62a28d6) by @JasonXuDeveloper
- perf(go): read and write maps of primitive keys and values as one block (7ac8b46) by @JasonXuDeveloper
- perf(core): fast modulo for the buckets and locks of built ConcurrentDictionaries on x64 (3d4baf2) by @JasonXuDeveloper
- perf(rust): the AVX2 string writer reuses the window its Latin-1 pass stopped at (a33ebc6) by @JasonXuDeveloper
- perf(core): unaligned 8-byte accesses on 32-bit x86, WebAssembly and .NET on Arm32 (20ddb6a) by @JasonXuDeveloper
- perf(core): Vector<T> text kernels for .NET Framework, and no Vector<T> on Mono (9bc357f) by @JasonXuDeveloper
- perf(rust): prefetch ahead while writing lists larger than the last-level cache (869408d) by @JasonXuDeveloper
- perf(core): Vector<byte> padding masks on Mono, with loads Mono compiles to SSE (bdefbd8) by @JasonXuDeveloper
- perf(core): keep the pool's hot fields on their own cache line wherever the runtime places them (4beee30) by @JasonXuDeveloper
- perf(unity): call the Burst kernels through function pointers and from shorter runs (8e5a0d6) by @JasonXuDeveloper
- perf(rust): the AVX-512 string writer reuses the window its Latin-1 pass stopped at (4e5465d) by @JasonXuDeveloper
- perf(core): check strided bools eight at a time, and move padded values over 8 bytes in lane groups on Arm64 (d67fba3) by @JasonXuDeveloper
- perf(core): copy 32 KB to 1 MB with 32-byte vectors where they beat Memmove on that vendor (59fd633) by @JasonXuDeveloper
- perf(core): write padded values over 16 bytes four or more at a time by AVX-512 VBMI pair permutes (2f75771) by @JasonXuDeveloper
- perf(core): keep the exact formatter class behind CachedFormatter<T> (6d711cb) by @JasonXuDeveloper
- perf(core): write raw Dictionary and HashSet entries as blocks (5627737) by @JasonXuDeveloper
- perf(core): check runs of values in Untrusted mode with vectors (1dfea08) by @JasonXuDeveloper
- perf(core): compare Latin-1 and ASCII strings eight bytes at a time on netstandard (39c56d3) by @JasonXuDeveloper
- perf(rust): lists of plain structs of fixed-size members as one block (92f469b) by @JasonXuDeveloper
- perf(codegen): write Go collection loops with direct calls, no func value an element (10222d4) by @JasonXuDeveloper
- perf(codegen): read and write Go grid items with direct calls (b946b44) by @JasonXuDeveloper
- perf(go): check blocks four values a step, one compare each (6845d4c) by @JasonXuDeveloper
- perf(go): copy wide UTF-16 code units as memory, narrow Latin-1 four a word (3a83aea) by @JasonXuDeveloper
- perf(core): read padded blocks with every byte written, into uninitialized arrays, in groups of whole memory vectors (57a7107) by @JasonXuDeveloper
- perf(core): mask padded raw structs with 32-byte vectors on Ice Lake (8ca958d) by @JasonXuDeveloper
- perf(core): box a formatter once on netstandard instead of per call (281d6f7) by @JasonXuDeveloper
- perf(generator): generic formatters take their type arguments' formatters (d902e73) by @JasonXuDeveloper
- perf(go): check sets and dictionary keys in a seeded table, read integer sets as one block (59ce8fa) by @JasonXuDeveloper
- perf(core): check lane-read bools in the pass that reads them (AVX2) (a9733a3) by @JasonXuDeveloper
- perf(core): grid windows for Arm64 partial lane groups (a37207d) by @JasonXuDeveloper
- perf(core): Ice Lake write groups load memory from 64-byte boundaries (c5cbb7d) by @JasonXuDeveloper
- perf(core): Ice Lake on Linux copies 2 to 32 KB blocks with Memmove (a02929d) by @JasonXuDeveloper
- perf(rust): decode short text with a back edge per char class (c95f16f) by @JasonXuDeveloper
- perf(generator): a direct call per level of a collection that holds itself (eede146) by @JasonXuDeveloper
- perf(generator): search each type once per Vector<T> query (c1d5fd4) by @JasonXuDeveloper
- perf(core): read a culture name read before without asking ICU again (1c586e9) by @JasonXuDeveloper
- perf(core): read a TimeZoneInfo's null rules inline (4627569) by @JasonXuDeveloper
- perf(core): one call per block copy of 65 bytes to 32 KB (d9b2cae) by @JasonXuDeveloper
- perf(core): look up the hash-walk plan only for a key a refill adds (5cce8a0) by @JasonXuDeveloper
- perf(core): merge raw keys and strings into an existing dictionary in one loop (cc52c4e) by @JasonXuDeveloper
- perf(core): compare an unchanged short string inside ReadStringOver (259900e) by @JasonXuDeveloper
- perf(core): store no unchanged string back in the dictionary merge (d5ac9a3) by @JasonXuDeveloper
- perf(generator): store no unchanged string into an existing object (9db6920) by @JasonXuDeveloper
🐛 Fixes (86)
- fix(typescript): defer class bodies past 128 open ones, as .NET does (483ba54) by @JasonXuDeveloper
- fix(typescript): throw a NinoError for [NinoUtf8] text that is not UTF-8 (7370488) by @JasonXuDeveloper
- fix(unity): reject a null alpha key count in a Gradient (163ba2e) by @JasonXuDeveloper
- fix(aspnetcore): keep collectible types out of the formatters' type cache (41aae6b) by @JasonXuDeveloper
- fix(generator): rebuild cached models when the project's formatter registrations change (fce70a3) by @JasonXuDeveloper
- fix(generator): find top-level calls through using aliases and using static (890b6f3) by @JasonXuDeveloper
- fix(generator): keep the model cache of closed projects only while a compilation of theirs is alive (8e685c5) by @JasonXuDeveloper
- fix(generator): lay out BCL structs from their real fields, not reference-assembly placeholders (36eb8d5) by @JasonXuDeveloper
- fix(generator): serialize tuples of eight items or more (46bd9f8) by @JasonXuDeveloper
- fix(generator): give formatters whose names differ only by case files of their own (be69b86) by @JasonXuDeveloper
- fix(core): keep removed assemblies out of later scans of every assembly (a1b88d4) by @JasonXuDeveloper
- fix(core): reject a null boxed value of a value type with ArgumentNullException (5a6e82b) by @JasonXuDeveloper
- fix(core): reject big-endian hosts on the raw top-level paths too (e99ba41) by @JasonXuDeveloper
- fix(core): count the levels of collections that hold themselves when reading (51ae012) by @JasonXuDeveloper
- fix(core): bind raw [NinoType] structs as raw in the runtime resolver too (2b3ac47) by @JasonXuDeveloper
- fix(core): remember type ids a polymorphic read did not find (40713b7) by @JasonXuDeveloper
- fix(core): decide raw types by one rule with and without dynamic code (e039b8c) by @JasonXuDeveloper
- fix(core): let only Nino.Core create and register subtype handlers (0f45ae4) by @JasonXuDeveloper
- fix(core): keep the layout attribute's typeId parameter name (d04f1f5) by @JasonXuDeveloper
- fix(core): lay out Int128 and UInt128 with their 16-byte alignment (dece2a7) by @JasonXuDeveloper
- fix(build): pack README.md and the icon from the repository root (427d260) by @JasonXuDeveloper
- fix(core): reject bytes Nino never writes, in every mode (b0a3612) by @JasonXuDeveloper
- fix(core): count the nesting of self-holding structs and collections on write (5dfd7f9) by @JasonXuDeveloper
- fix(codegen): read bools and nullable flags of raw values through the checking reads (e210c5b) by @JasonXuDeveloper
- fix(typescript): reject malformed sections, keyed members with bytes left and repeated keys (569427e) by @JasonXuDeveloper
- fix(build): write the target-framework attributes file under obj/ again (13edaa5) by @JasonXuDeveloper
- fix(runtimes): read DateTime kind 3, which .NET writes for ambiguous local times (6b90133) by @JasonXuDeveloper
- fix(core): check the bools of tuples and pairs read as blocks (f3adcb9) by @JasonXuDeveloper
- fix(benchmarks): register GenericPolymorphicTest's payload sizes (87dc9e3) by @JasonXuDeveloper
- fix(benchmarks): name the class of a one-class report in the merged and compact tables (39024ed) by @JasonXuDeveloper
- fix(generator): write closed generic [NinoType] structs by their formatter everywhere (8746178) by @JasonXuDeveloper
- fix(core): read and write value tuples of eight items or more where the formatter is resolved at run time (2f525c8) by @JasonXuDeveloper
- fix(core): count open struct bodies when a class that cannot be deferred nests too deep on write (403ebe1) by @JasonXuDeveloper
- fix(collections): bound hash collisions when reading a ConcurrentDictionary (6697b2f) by @JasonXuDeveloper
- fix(codegen): closed generic [NinoType] structs as members are generic, not raw (43200df) by @JasonXuDeveloper
- fix(unity): test each instruction set where its Burst intrinsics are used (5b0efe5) by @JasonXuDeveloper
- fix(generator): read closed generic subtypes of generic slots, and reject null [NinoType] collection elements (cceab9b) by @JasonXuDeveloper
- fix(core): declare NinoKernels in every build of Nino.Core (bdc53c1) by @JasonXuDeveloper
- fix(go): detect AVX-512 on macOS as golang.org/x/sys/cpu does (1eae445) by @JasonXuDeveloper
- fix(aspnetcore): only invalid data is a 400 (a4a9bb3) by @JasonXuDeveloper
- fix(unitytypes): RectOffset's presence byte as every other formatter reads it (881afd2) by @JasonXuDeveloper
- fix(typescript): the npm package's ninoVersion is its version (d3e5096) by @JasonXuDeveloper
- fix(typescript): engines names the oldest Node the runtime runs on (fc3259e) by @JasonXuDeveloper
- fix(typescript): reject a decimal with a scale past 28 or stray flag bits (82107f2) by @JasonXuDeveloper
- fix(typescript): reject DateTime ticks past DateTime.MaxValue (8436eb9) by @JasonXuDeveloper
- fix(codegen): Go code calls UTF8Ptr and NullableUTF8, not the deprecated names (cdbbe46) by @JasonXuDeveloper
- fix(generator): name the type in the "cannot construct" message, not its alias (3425a36) by @JasonXuDeveloper
- fix(core): keep the SSSE3 block shuffle under NativeAOT (d8381f5) by @JasonXuDeveloper
- fix(rust): derive counts a UTF-8 member as one byte at least (b0f2e24) by @JasonXuDeveloper
- fix(analyzer): NINO007 for a generic NinoType closed over a type without a formatter (ab8eb02) by @JasonXuDeveloper
- fix(core): a rejected read leaves no uninitialized array in the caller's object (1f4cdc2) by @JasonXuDeveloper
- fix(go): a second NaN key in a map read from the data is a repeated key (4833d42) by @JasonXuDeveloper
- fix(typescript): bound hash flooding of number and bigint keys (NinoKeyGuard) (370d4be) by @JasonXuDeveloper
- fix(go): build on 32-bit targets, and test string sets at the pointer's own size (3f693f9) by @JasonXuDeveloper
- fix(core): keep the recursive array and list reads' frames as before (da2da2f) by @JasonXuDeveloper
- fix(core): 64-byte block kernels on every Intel CPU but the Ice Lake generation (fa4b61b) by @JasonXuDeveloper
- fix(core): clear rejected block reads instead of zeroing their arrays (fd9d92f) by @JasonXuDeveloper
- fix(typescript): bound hash flooding of string keys V8 hashes by value (8e75d5a) by @JasonXuDeveloper
- fix(core): read back only the cultures .NET ships, so names from the data cannot grow the process (ecd1a39) by @JasonXuDeveloper
…truncated; see the full changelog: v4.0.0-rc.7...v4.0.0-rc.8
Contributors
Thanks to everyone who contributed to this release: @JasonXuDeveloper
Full Changelog: v4.0.0-rc.7...v4.0.0-rc.8
📊 v3 3.3.13 vs v4 (same machine)
Nino v3 3.3.13 (NuGet) against v4.0.0-rc.8, back to back on the same runners
The v3 build of benchmarks/Nino.Benchmark (the Nino and MemoryPack cases of the same SimpleTest, built against Nino 3.3.13 from nuget.org) ran directly before the v4
SimpleTest run below, its serialize and its deserialize cases each on the runner of the same v4 cases, with
the same BenchmarkDotNet settings
(--launchCount 3 --warmupCount 5 --iterationCount 15). MemoryPack runs in both, so its two columns show how much the runner moved.
v4 vs v3
| case | v3 3.3.13 | v4 | v4 vs v3 | MemoryPack (v4 run) | MemoryPack (v3 run) |
|---|---|---|---|---|---|
| SerializeSimpleClass | 185.39 ns | 93.58 ns | 0.50x | 389.82 ns | 397.80 ns |
| SerializeSimpleClasses | 24.06 μs | 10.15 μs | 0.42x | 40.68 μs | 39.83 μs |
| SerializeSimpleStruct | 3.46 ns | 4.52 ns | 1.31x | 5.11 ns | 5.14 ns |
| SerializeSimpleStructs | 22.34 ns | 18.32 ns | 0.82x | 22.21 ns | 23.14 ns |
| SerializeVectors | 3.31 μs | 3.26 μs | 0.98x | 3.27 μs | 3.33 μs |
| DeserializeSimpleClass | 599.27 ns | 392.62 ns | 0.66x | 804.52 ns | 797.51 ns |
| DeserializeSimpleClasses | 65.05 μs | 44.21 μs | 0.68x | 80.79 μs | 79.23 μs |
| DeserializeSimpleStruct | 0.70 ns | 0.71 ns | on par (both too fast to measure) | 1.45 ns | 1.47 ns |
| DeserializeSimpleStructs | 92.41 ns | 85.79 ns | 0.93x | 101.99 ns | 109.84 ns |
| DeserializeVectors | 126.81 μs | 124.33 μs | 0.98x | 125.10 μs | 126.51 μs |
v4 vs v3 = mean(v4) / mean(v3); below 1.00x means v4 is faster.
MemoryPack drift (v4 run / v3 run): min 0.93x, max 1.02x (same code in both runs; the spread is the noise floor of this comparison).
v3 3.3.13 runners
BenchmarkDotNet v0.15.8, Linux Ubuntu 26.04.1 LTS (Resolute Raccoon)
AMD EPYC 7763 3.21GHz, 1 CPU, 4 logical and 2 physical cores
.NET SDK 10.0.403
[Host] : .NET 10.0.14 (10.0.14, 10.0.1426.52326), X64 RyuJIT x86-64-v3
ShortRun-.NET 10.0 : .NET 10.0.14 (10.0.14, 10.0.1426.52326), X64 RyuJIT x86-64-v3
Job=ShortRun-.NET 10.0 Runtime=.NET 10.0 Server=True
IterationCount=15 LaunchCount=3 WarmupCount=5
The cases ran in parallel on several runners (every serializer's run of a case on one):
- SimpleTest (NinoSerializeSimpleClass to MemoryPackSerializeVectors): AMD EPYC 7763 3.21GHz, 1 CPU, 4 logical and 2 physical cores
- SimpleTest (NinoDeserializeSimpleClass to MemoryPackDeserializeVectors): AMD EPYC 7763 3.15GHz, 1 CPU, 4 logical and 2 physical cores
The v3 report: benchmark-v3-full.md.
📊 Performance Report
Click to expand benchmark results
BenchmarkDotNet v0.15.8, Linux Ubuntu 26.04.1 LTS (Resolute Raccoon)
AMD EPYC 7763 2.72GHz, 1 CPU, 4 logical and 2 physical cores
.NET SDK 10.0.403
[Host] : .NET 10.0.14 (10.0.14, 10.0.1426.52326), X64 RyuJIT x86-64-v3
ShortRun-.NET 10.0 : .NET 10.0.14 (10.0.14, 10.0.1426.52326), X64 RyuJIT x86-64-v3
Job=ShortRun-.NET 10.0 Runtime=.NET 10.0 Server=True
IterationCount=15 LaunchCount=3 WarmupCount=5
ReferenceTest
| Method | Mean | Ratio | Payload | Allocated |
|---|---|---|---|---|
| NinoDeserializeGraph | 6.10 μs | 1.00 | - | 14104 B |
| MemoryPackDeserializeGraph | 20.10 μs | 3.30 | - | - |
| NinoSerializeGraph | 5.99 μs | 1.00 | 5,830 B | - |
| MemoryPackSerializeGraph | 30.04 μs | 5.02 | 5,584 B | - |
PooledTest
| Method | Mean | Ratio | Payload | Allocated |
|---|---|---|---|---|
| NinoDeserializeNew | 382.03 ns | 1.00 | - | 2296 B |
| NinoDeserializePooled | 232.04 ns | 0.61 | - | - |
| MemoryPackDeserializeNew | 732.56 ns | 1.92 | - | 2536 B |
| MessagePackDeserializeNew | 1.78 μs | 4.67 | - | 2536 B |
| ProtobufDeserializeNew | 6.10 μs | 15.98 | - | 3544 B |
| MemoryPackDeserializeOverwrite | 607.95 ns | 1.59 | - | 1080 B |
SimpleTest
| Method | Mean | Ratio | Payload | Allocated |
|---|---|---|---|---|
| NinoSerializeSimpleClass | 93.58 ns | 1.00 | 1,069 B | - |
| MemoryPackSerializeSimpleClass | 389.82 ns | 4.17 | 1,148 B | - |
| MessagePackSerializeSimpleClass | 1.11 μs | 11.91 | 1,068 B | - |
| ProtobufSerializeSimpleClass | 3.78 μs | 40.43 | 1,257 B | 80 B |
| NinoSerializeSimpleClasses | 10.15 μs | 1.00 | 106,904 B | - |
| MemoryPackSerializeSimpleClasses | 40.68 μs | 4.01 | 114,804 B | - |
| MessagePackSerializeSimpleClasses | 111.92 μs | 11.03 | 106,803 B | - |
| ProtobufSerializeSimpleClasses | 371.69 μs | 36.64 | 125,460 B | 8000 B |
| NinoSerializeSimpleStruct | 4.52 ns | 1.00 | 12 B | - |
| MemoryPackSerializeSimpleStruct | 5.11 ns | 1.13 | 12 B | - |
| MessagePackSerializeSimpleStruct | 57.83 ns | 12.79 | 12 B | - |
| ProtobufSerializeSimpleStruct | 157.53 ns | 34.83 | 16 B | - |
| NinoSerializeSimpleStructs | 18.32 ns | 1.00 | 1,204 B | - |
| MemoryPackSerializeSimpleStructs | 22.21 ns | 1.21 | 1,204 B | - |
| MessagePackSerializeSimpleStructs | 3.56 μs | 194.26 | 1,203 B | - |
| ProtobufSerializeSimpleStructs | 10.27 μs | 560.56 | 1,784 B | - |
| NinoSerializeVectors | 3.26 μs | 1.00 | 160,004 B | - |
| MemoryPackSerializeVectors | 3.27 μs | 1.01 | 160,004 B | - |
| MessagePackSerializeVectors | 165.17 μs | 50.72 | 210,003 B | - |
| ProtobufSerializeVectors | 605.85 μs | 186.06 | 220,000 B | - |
| NinoDeserializeSimpleClass | 392.62 ns | 1.00 | - | 2288 B |
| MemoryPackDeserializeSimpleClass | 804.52 ns | 2.06 | - | 2528 B |
| MessagePackDeserializeSimpleClass | 1.86 μs | 4.74 | - | 2528 B |
| ProtobufDeserializeSimpleClass | 6.43 μs | 16.42 | - | - |
| NinoDeserializeSimpleClasses | 44.21 μs | 1.00 | - | 229624 B |
| MemoryPackDeserializeSimpleClasses | 80.79 μs | 1.83 | - | 253624 B |
| MessagePackDeserializeSimpleClasses | 200.87 μs | 4.55 | - | 253624 B |
| ProtobufDeserializeSimpleClasses | 630.10 μs | 14.27 | - | 354424 B |
| NinoDeserializeSimpleStruct | 0.71 ns | 1.00 | - | - |
| MemoryPackDeserializeSimpleStruct | 1.45 ns | 2.05 | - | - |
| MessagePackDeserializeSimpleStruct | 77.63 ns | 109.70 | - | - |
| ProtobufDeserializeSimpleStruct | 226.82 ns | 320.54 | - | - |
| NinoDeserializeSimpleStructs | 85.79 ns | 1.00 | - | 1224 B |
| MemoryPackDeserializeSimpleStructs | 101.99 ns | 1.19 | - | 1224 B |
| MessagePackDeserializeSimpleStructs | 3.51 μs | 41.00 | - | 1224 B |
| ProtobufDeserializeSimpleStructs | 10.32 μs | 120.48 | - | 1224 B |
| NinoDeserializeVectors | 124.33 μs | 1.00 | - | 160033 B |
| MemoryPackDeserializeVectors | 125.10 μs | 1.01 | - | 160031 B |
| MessagePackDeserializeVectors | 391.42 μs | 3.15 | - | 160035 B |
| ProtobufDeserializeVectors | 715.11 μs | 5.76 | - | 160041 B |
FixedSizeTest
| Method | Mean | Ratio | Payload | Allocated |
|---|---|---|---|---|
| NinoDeserializeFixedClass | 10.41 ns | 1.00 | - | 56 B |
| MemoryPackDeserializeFixedClass | 22.61 ns | 2.18 | - | 56 B |
| MessagePackDeserializeFixedClass | 174.03 ns | 16.74 | - | 56 B |
| ProtobufDeserializeFixedClass | 308.66 ns | 29.69 | - | 56 B |
| NinoSerializeFixedClass | 10.22 ns | 1.00 | 41 B | 72 B |
| MemoryPackSerializeFixedClass | 26.25 ns | 2.57 | 41 B | 72 B |
| MessagePackSerializeFixedClass | 120.39 ns | 11.79 | 69 B | - |
| ProtobufSerializeFixedClass | 283.01 ns | 27.71 | 53 B | 80 B |
| NinoDeserializeFixedClasses | 1.16 μs | 1.00 | - | 6424 B |
| MemoryPackDeserializeFixedClasses | 1.39 μs | 1.20 | - | 6424 B |
| MessagePackDeserializeFixedClasses | 25.54 μs | 21.99 | - | 6424 B |
| ProtobufDeserializeFixedClasses | 19.23 μs | 16.56 | - | - |
| NinoSerializeFixedClasses | 467.38 ns | 1.00 | 4,104 B | 4128 B |
| MemoryPackSerializeFixedClasses | 1.82 μs | 3.90 | 4,104 B | 4128 B |
| MessagePackSerializeFixedClasses | 8.34 μs | 17.86 | 6,903 B | - |
| ProtobufSerializeFixedClasses | 20.97 μs | 44.87 | 5,488 B | - |
| NinoDeserializeFixedStruct | 0.40 ns | 1.12 | - | - |
| MemoryPackDeserializeFixedStruct | 0.97 ns | 2.74 | - | - |
| MessagePackDeserializeFixedStruct | 175.84 ns | 496.73 | - | - |
| ProtobufDeserializeFixedStruct | 275.23 ns | 777.48 | - | - |
| NinoSerializeFixedStruct | 9.30 ns | 1.00 | 40 B | 64 B |
| MemoryPackSerializeFixedStruct | 9.61 ns | 1.04 | 40 B | 64 B |
| MessagePackSerializeFixedStruct | 115.75 ns | 12.47 | 69 B | - |
| ProtobufSerializeFixedStruct | 268.95 ns | 28.98 | 53 B | 80 B |
ScatteredTest
| Method | Mean | Ratio | Payload | Allocated |
|---|---|---|---|---|
| NinoSerializeHotShapes | 16.82 μs | 1.00 | 114,692 B | - |
| MemoryPackSerializeHotShapes | 201.16 μs | 11.96 | 98,308 B | - |
| MessagePackSerializeHotShapes | 336.70 μs | 20.01 | 110,211 B | - |
| ProtobufSerializeHotShapes | 1.09 ms | 64.72 | 126,841 B | - |
| NinoSerializeInOrderShapes | 3.06 ms | 1.00 | 14,000,004 B | - |
| MemoryPackSerializeInOrderShapes | 25.28 ms | 8.28 | 12,000,004 B | - |
| MessagePackSerializeInOrderShapes | 37.05 ms | 12.13 | 15,368,549 B | - |
| ProtobufSerializeInOrderShapes | 137.56 ms | 45.04 | 16,483,481 B | - |
| NinoDeserializeScatteredItemsInto | 64.17 ms | 1.00 | - | - |
| MemoryPackDeserializeScatteredItemsInto | 153.32 ms | 2.39 | - | 47920000 B |
| NinoSerializeScatteredItems | 91.84 ms | 1.00 | 24,888,894 B | - |
| MemoryPackSerializeScatteredItems | 249.70 ms | 2.72 | 31,888,894 B | - |
| MessagePackSerializeScatteredItems | 244.07 ms | 2.66 | 22,738,657 B | - |
| ProtobufSerializeScatteredItems | 306.14 ms | 3.33 | 23,570,420 B | - |
| NinoSerializeScatteredShapes | 22.71 ms | 1.01 | 14,000,004 B | - |
| MemoryPackSerializeScatteredShapes | 138.23 ms | 6.14 | 12,000,004 B | - |
| MessagePackSerializeScatteredShapes | 148.00 ms | 6.58 | 15,368,549 B | - |
| ProtobufSerializeScatteredShapes | 245.60 ms | 10.91 | 16,483,481 B | - |
PooledGraphTest
| Method | Mean | Ratio | Payload | Allocated |
|---|---|---|---|---|
| NinoLeavesNew | 3.04 μs | 1.00 | - | 13656 B |
| NinoLeavesPooledElements | 3.16 μs | 1.04 | - | 856 B |
| NinoLeavesInPlace | 469.66 ns | 0.15 | - | - |
| MemoryPackLeavesNew | 3.87 μs | 1.27 | - | 13656 B |
| MessagePackLeavesNew | 10.01 μs | 3.29 | - | - |
| ProtobufLeavesNew | 31.25 μs | 10.27 | - | - |
| MemoryPackLeavesOverwrite | 5.12 μs | 1.68 | - | 12800 B |
| NinoNodeNew | 474.97 ns | 1.00 | - | 2192 B |
| NinoNodePooled | 157.06 ns | 0.33 | - | - |
| MemoryPackNodeNew | 688.48 ns | 1.45 | - | 2192 B |
| MessagePackNodeNew | 1.88 μs | 3.97 | - | - |
| ProtobufNodeNew | 6.41 μs | 13.51 | - | - |
| MemoryPackNodeOverwrite | 621.78 ns | 1.31 | - | 1024 B |
| NinoPlainNew | 3.18 μs | 1.00 | - | 12856 B |
| MemoryPackPlainNew | 4.22 μs | 1.33 | - | 12856 B |
| MessagePackPlainNew | 10.96 μs | 3.45 | - | 12856 B |
| ProtobufPlainNew | 31.58 μs | 9.95 | - | 16088 B |
UntrustedTest
| Method | Mean | Ratio | Payload | Allocated |
|---|---|---|---|---|
| NinoDeserializeUntrustedList | 2.65 μs | 1.00 | - | 24056 B |
| MemoryPackDeserializeUntrustedList | 1.70 μs | 0.64 | - | 24056 B |
BclTypesTest
| Method | Mean | Ratio | Payload | Allocated |
|---|---|---|---|---|
| NinoDeserializeBigInteger | 35.61 ns | 1.00 | - | 256 B |
| MessagePackDeserializeBigInteger | 104.96 ns | 2.95 | - | 256 B |
| NinoSerializeBigInteger | 13.43 ns | 1.00 | 203 B | - |
| MessagePackSerializeBigInteger | 39.76 ns | 2.96 | 201 B | - |
| NinoDeserializeBitArray | 50.94 ns | 1.03 | - | 336 B |
| MessagePackDeserializeBitArray | 4.04 μs | 82.01 | - | 184 B |
| NinoSerializeBitArray | 25.69 ns | 1.00 | 129 B | - |
| MessagePackSerializeBitArray | 2.37 μs | 92.15 | 1,003 B | - |
| NinoDeserializeCultureInfo | 15.90 ns | 1.00 | - | 32 B |
| MemoryPackDeserializeCultureInfo | 41.68 ns | 2.62 | - | 64 B |
| NinoSerializeCultureInfo | 2.88 ns | 1.00 | 6 B | - |
| MemoryPackSerializeCultureInfo | 15.22 ns | 5.29 | 13 B | - |
| NinoDeserializeTimeZoneInfo | 7.92 ns | 1.00 | - | 32 B |
| MemoryPackDeserializeTimeZoneInfo | 187.22 ns | 23.65 | - | 680 B |
| NinoSerializeTimeZoneInfo | 3.72 ns | 1.00 | 5 B | - |
| MemoryPackSerializeTimeZoneInfo | 109.09 ns | 29.31 | 102 B | 216 B |
| NinoDeserializeBlockingCollection | 5.33 μs | 1.00 | - | 12792 B |
| MemoryPackDeserializeBlockingCollection | 29.19 μs | 5.47 | - | 17984 B |
| NinoSerializeBlockingCollection | 5.38 μs | 1.00 | 4,004 B | 4096 B |
| MemoryPackSerializeBlockingCollection | 11.85 μs | 2.20 | 4,004 B | 72 B |
| NinoDeserializeImmutableDictionary | 21.13 μs | 1.00 | - | 68088 B |
| MemoryPackDeserializeImmutableDictionary | 112.38 μs | 5.32 | - | 56096 B |
| MessagePackDeserializeImmutableDictionary | 121.53 μs | 5.76 | - | 56097 B |
| ProtobufDeserializeImmutableDictionary | 164.54 μs | 7.80 | - | 56080 B |
| NinoSerializeImmutableDictionary | 3.36 μs | 1.00 | 8,004 B | - |
| MemoryPackSerializeImmutableDictionary | 20.05 μs | 5.97 | 8,004 B | - |
| MessagePackSerializeImmutableDictionary | 27.17 μs | 8.09 | 5,563 B | - |
| ProtobufSerializeImmutableDictionary | 73.47 μs | 21.87 | 7,849 B | 208 B |
| NinoDeserializeImmutableHashSet | 21.30 μs | 1.00 | - | 64096 B |
| MemoryPackDeserializeImmutableHashSet | 106.27 μs | 5.00 | - | 56104 B |
| MessagePackDeserializeImmutableHashSet | 108.21 μs | 5.09 | - | - |
| ProtobufDeserializeImmutableHashSet | 118.41 μs | 5.57 | - | 56120 B |
| NinoSerializeImmutableHashSet | 3.07 μs | 1.00 | 4,004 B | - |
| MemoryPackSerializeImmutableHashSet | 26.26 μs | 8.56 | 4,004 B | - |
| MessagePackSerializeImmutableHashSet | 18.62 μs | 6.07 | 2,619 B | - |
| ProtobufSerializeImmutableHashSet | 44.20 μs | 14.41 | 2,872 B | 168 B |
| NinoDeserializeImmutableQueue | 8.04 μs | 1.00 | - | 36064 B |
| MemoryPackDeserializeImmutableQueue | 9.90 μs | 1.23 | - | 32112 B |
| MessagePackDeserializeImmutableQueue | 22.98 μs | 2.86 | - | 72024 B |
| ProtobufDeserializeImmutableQueue | 35.66 μs | 4.44 | - | 72000 B |
| NinoSerializeImmutableQueue | 1.95 μs | 1.00 | 4,004 B | - |
| MemoryPackSerializeImmutableQueue | 8.58 μs | 4.41 | 4,004 B | - |
| MessagePackSerializeImmutableQueue | 6.74 μs | 3.46 | 2,619 B | 48 B |
| ProtobufSerializeImmutableQueue | 14.64 μs | 7.51 | 2,872 B | - |
| NinoDeserializeImmutableSortedSet | 13.71 μs | 1.00 | - | 52056 B |
| MemoryPackDeserializeImmutableSortedSet | 90.38 μs | 6.60 | - | 48089 B |
| MessagePackDeserializeImmutableSortedSet | 90.40 μs | 6.60 | - | 48088 B |
| ProtobufDeserializeImmutableSortedSet | 36.29 μs | 2.65 | - | 52120 B |
| NinoSerializeImmutableSortedSet | 2.81 μs | 1.00 | 4,004 B | - |
| MemoryPackSerializeImmutableSortedSet | 14.27 μs | 5.09 | 4,004 B | - |
| MessagePackSerializeImmutableSortedSet | 10.81 μs | 3.85 | 2,619 B | - |
| ProtobufSerializeImmutableSortedSet | 24.89 μs | 8.87 | 2,872 B | 168 B |
| NinoDeserializeImmutableStack | 7.99 μs | 1.00 | - | 36024 B |
| MemoryPackDeserializeImmutableStack | 10.30 μs | 1.29 | - | 32040 B |
| MessagePackDeserializeImmutableStack | 13.44 μs | 1.69 | - | 36024 B |
| ProtobufDeserializeImmutableStack | 25.47 μs | 3.19 | - | 32000 B |
| NinoSerializeImmutableStack | 2.95 μs | 1.00 | 4,004 B | - |
| MemoryPackSerializeImmutableStack | 15.64 μs | 5.32 | 4,004 B | 4112 B |
| MessagePackSerializeImmutableStack | 5.87 μs | 2.00 | 2,619 B | 40 B |
| ProtobufSerializeImmutableStack | 15.54 μs | 5.29 | 2,872 B | - |
| NinoDeserializeImmutableSortedDictionary | 14.07 μs | 1.00 | - | 56072 B |
| MemoryPackDeserializeImmutableSortedDictionary | 76.21 μs | 5.42 | - | 48112 B |
| MessagePackDeserializeImmutableSortedDictionary | 79.90 μs | 5.69 | - | 48112 B |
| ProtobufDeserializeImmutableSortedDictionary | 140.46 μs | 10.00 | - | 104520 B |
| NinoSerializeImmutableSortedDictionary | 3.53 μs | 1.00 | 8,004 B | - |
| MemoryPackSerializeImmutableSortedDictionary | 8.90 μs | 2.52 | 8,004 B | - |
| MessagePackSerializeImmutableSortedDictionary | 12.59 μs | 3.57 | 5,563 B | - |
| ProtobufSerializeImmutableSortedDictionary | 41.43 μs | 11.75 | 7,849 B | 208 B |
| NinoDeserializeLazy | 20.28 ns | 1.00 | - | 112 B |
| MemoryPackDeserializeLazy | 35.47 ns | 1.75 | - | - |
| MessagePackDeserializeLazy | 86.84 ns | 4.29 | - | 232 B |
| NinoSerializeLazy | 4.65 ns | 1.00 | 24 B | - |
| MemoryPackSerializeLazy | 31.91 ns | 6.86 | 31 B | - |
| MessagePackSerializeLazy | 38.75 ns | 8.33 | 23 B | - |
| NinoDeserializeMemory | 275.48 ns | 1.00 | - | 4056 B |
| MemoryPackDeserializeMemory | 327.22 ns | 1.19 | - | 4056 B |
| MessagePackDeserializeMemory | 4.51 μs | 16.38 | - | 4056 B |
| NinoSerializeMemory | 32.59 ns | 1.00 | 4,004 B | - |
| MemoryPackSerializeMemory | 56.84 ns | 1.74 | 4,004 B | - |
| MessagePackSerializeMemory | 3.05 μs | 93.47 | 2,619 B | - |
| NinoDeserializeReadOnlySequence | 276.53 ns | 1.00 | - | 4064 B |
| MemoryPackDeserializeReadOnlySequence | 331.69 ns | 1.20 | - | 4064 B |
| MessagePackDeserializeReadOnlySequence | 4.54 μs | 16.41 | - | 4064 B |
| NinoSerializeReadOnlySequence | 34.83 ns | 1.00 | 4,004 B | - |
| MemoryPackSerializeReadOnlySequence | 59.27 ns | 1.70 | 4,004 B | - |
| MessagePackSerializeReadOnlySequence | 4.64 μs | 133.21 | 2,619 B | - |
| NinoDeserializeStringBuilder | 151.98 ns | 1.00 | - | 2072 B |
| MemoryPackDeserializeStringBuilder | 163.36 ns | 1.07 | - | 2072 B |
| MessagePackDeserializeStringBuilder | 343.22 ns | 2.26 | - | 4096 B |
| NinoSerializeStringBuilder | 21.91 ns | 1.00 | 2,003 B | - |
| MemoryPackSerializeStringBuilder | 33.89 ns | 1.55 | 2,004 B | - |
| MessagePackSerializeStringBuilder | 234.43 ns | 10.70 | 1,003 B | 2024 B |
| NinoDeserializeUri | 77.48 ns | 1.00 | - | 184 B |
| MemoryPackDeserializeUri | 85.50 ns | 1.10 | - | 184 B |
| MessagePackDeserializeUri | 106.27 ns | 1.37 | - | 184 B |
| ProtobufDeserializeUri | 375.22 ns | 4.84 | - | 184 B |
| NinoSerializeUri | 6.63 ns | 1.00 | 53 B | - |
| MemoryPackSerializeUri | 21.87 ns | 3.30 | 60 B | - |
| MessagePackSerializeUri | 34.93 ns | 5.27 | 54 B | - |
| ProtobufSerializeUri | 85.69 ns | 12.93 | 54 B | - |
| NinoDeserializeVersion | 7.10 ns | 1.00 | - | 32 B |
| MemoryPackDeserializeVersion | 17.63 ns | 2.49 | - | 32 B |
| MessagePackDeserializeVersion | 85.52 ns | 12.08 | - | 104 B |
| NinoSerializeVersion | 1.77 ns | 1.00 | 16 B | - |
| MemoryPackSerializeVersion | 15.94 ns | 8.98 | 17 B | - |
| MessagePackSerializeVersion | 56.03 ns | 31.57 | 8 B | 40 B |
| NinoDeserializeLookup | 426.85 ns | 1.00 | - | 5152 B |
| MemoryPackDeserializeLookup | 602.12 ns | 1.41 | - | 5576 B |
| MessagePackDeserializeLookup | 4.74 μs | 11.12 | - | 4127 B |
| NinoSerializeLookup | 2.10 μs | 1.00 | 4,084 B | 320 B |
| MemoryPackSerializeLookup | 7.21 μs | 3.43 | 4,094 B | 320 B |
| MessagePackSerializeLookup | 3.73 μs | 1.77 | 2,667 B | 368 B |
| NinoDeserializeReadOnlyCollection | 192.78 ns | 1.00 | - | 4048 B |
| MemoryPackDeserializeReadOnlyCollection | 236.15 ns | 1.23 | - | 4048 B |
| MessagePackDeserializeReadOnlyCollection | 4.03 μs | 20.90 | - | 3383 B |
| NinoSerializeReadOnlyCollection | 1.93 μs | 1.00 | 4,004 B | 40 B |
| MemoryPackSerializeReadOnlyCollection | 6.80 μs | 3.52 | 4,004 B | 40 B |
| MessagePackSerializeReadOnlyCollection | 4.27 μs | 2.21 | 2,619 B | 40 B |
GenericPolymorphicTest
| Method | Mean | Ratio | Payload | Allocated |
|---|---|---|---|---|
| NinoDeserializeGenericIntBase | 11.45 μs | 1.00 | - | 32024 B |
| MemoryPackDeserializeGenericIntBase | 46.76 μs | 4.08 | - | 32024 B |
| ProtobufDeserializeGenericIntBase | 225.10 μs | 19.66 | - | - |
| NinoSerializeGenericIntBase | 3.36 μs | 1.00 | 8,004 B | - |
| MemoryPackSerializeGenericIntBase | 54.89 μs | 16.34 | 6,004 B | - |
| ProtobufSerializeGenericIntBase | 123.16 μs | 36.67 | 5,967 B | - |
| NinoDeserializeGenericManagedTupleBase | 26.85 μs | 1.00 | - | 72024 B |
| MemoryPackDeserializeGenericManagedTupleBase | 70.57 μs | 2.63 | - | - |
| ProtobufDeserializeGenericManagedTupleBase | 329.18 μs | 12.26 | - | 72037 B |
| NinoSerializeGenericManagedTupleBase | 9.74 μs | 1.00 | 14,004 B | - |
| MemoryPackSerializeGenericManagedTupleBase | 106.02 μs | 10.89 | 19,004 B | - |
| ProtobufSerializeGenericManagedTupleBase | 209.97 μs | 21.57 | 15,253 B | - |
| NinoDeserializeGenericOpenClassTupleBase | 23.34 μs | 1.00 | - | 72024 B |
| MemoryPackDeserializeGenericOpenClassTupleBase | 66.07 μs | 2.83 | - | 72024 B |
| ProtobufDeserializeGenericOpenClassTupleBase | 397.99 μs | 17.05 | - | 72037 B |
| NinoSerializeGenericOpenClassTupleBase | 8.60 μs | 1.00 | 24,004 B | - |
| MemoryPackSerializeGenericOpenClassTupleBase | 107.41 μs | 12.50 | 19,004 B | - |
| ProtobufSerializeGenericOpenClassTupleBase | 267.53 μs | 31.13 | 13,681 B | - |
| NinoDeserializeGenericSealedClassTupleBase | 23.32 μs | 1.00 | - | 72024 B |
| MemoryPackDeserializeGenericSealedClassTupleBase | 64.91 μs | 2.78 | - | 72024 B |
| ProtobufDeserializeGenericSealedClassTupleBase | 402.68 μs | 17.27 | - | 72037 B |
| NinoSerializeGenericSealedClassTupleBase | 8.68 μs | 1.00 | 21,004 B | - |
| MemoryPackSerializeGenericSealedClassTupleBase | 102.38 μs | 11.79 | 19,004 B | - |
| ProtobufSerializeGenericSealedClassTupleBase | 271.98 μs | 31.33 | 13,681 B | - |
| NinoDeserializeGenericUnmanagedTupleBase | 14.32 μs | 1.00 | - | 40024 B |
| MemoryPackDeserializeGenericUnmanagedTupleBase | 46.45 μs | 3.24 | - | 40024 B |
| ProtobufDeserializeGenericUnmanagedTupleBase | 277.94 μs | 19.41 | - | 40032 B |
| NinoSerializeGenericUnmanagedTupleBase | 5.48 μs | 1.00 | 16,004 B | - |
| MemoryPackSerializeGenericUnmanagedTupleBase | 57.69 μs | 10.55 | 18,004 B | - |
| ProtobufSerializeGenericUnmanagedTupleBase | 182.13 μs | 33.29 | 10,253 B | - |
| NinoDeserializeNonGenericBase | 11.58 μs | 1.00 | - | 32024 B |
| MemoryPackDeserializeNonGenericBase | 25.35 μs | 2.19 | - | 29297 B |
| MessagePackDeserializeNonGenericBase | 70.17 μs | 6.06 | - | 32024 B |
| ProtobufDeserializeNonGenericBase | 215.82 μs | 18.64 | - | 32030 B |
| NinoSerializeNonGenericBase | 3.36 μs | 1.00 | 8,004 B | - |
| MemoryPackSerializeNonGenericBase | 40.27 μs | 12.00 | 6,004 B | - |
| MessagePackSerializeNonGenericBase | 50.14 μs | 14.94 | 4,003 B | - |
| ProtobufSerializeNonGenericBase | 120.86 μs | 36.02 | 5,967 B | - |
CommonTypesTest
| Method | Mean | Ratio | Payload | Allocated |
|---|---|---|---|---|
| NinoDeserializeOrders | 20.60 μs | 1.00 | - | 88552 B |
| MemoryPackDeserializeOrders | 27.37 μs | 1.33 | - | 88552 B |
| MessagePackDeserializeOrders | 100.62 μs | 4.89 | - | 88552 B |
| ProtobufDeserializeOrders | 102.44 μs | 4.98 | - | 98986 B |
| NinoSerializeOrders | 4.50 μs | 1.00 | 23,726 B | - |
| MemoryPackSerializeOrders | 11.37 μs | 2.53 | 27,990 B | - |
| MessagePackSerializeOrders | 46.47 μs | 10.32 | 15,332 B | - |
| ProtobufSerializeOrders | 71.17 μs | 15.80 | 15,843 B | 4000 B |
| NinoDeserializePlayers | 25.36 μs | 1.00 | - | 120056 B |
| MemoryPackDeserializePlayers | 36.60 μs | 1.44 | - | 120056 B |
| MessagePackDeserializePlayers | 97.47 μs | 3.85 | - | 120056 B |
| ProtobufDeserializePlayers | 212.30 μs | 8.38 | - | 140088 B |
| NinoSerializePlayers | 6.37 μs | 1.00 | 36,164 B | - |
| MemoryPackSerializePlayers | 19.70 μs | 3.09 | 43,364 B | - |
| MessagePackSerializePlayers | 50.72 μs | 7.96 | 25,563 B | - |
| ProtobufSerializePlayers | 127.48 μs | 20.00 | 33,579 B | 4000 B |
| NinoDeserializeInventory | 206.60 ns | 1.00 | - | 8024 B |
| MemoryPackDeserializeInventory | 215.88 ns | 1.05 | - | 8024 B |
| MessagePackDeserializeInventory | 12.37 μs | 59.96 | - | 8024 B |
| ProtobufDeserializeInventory | 28.41 μs | 137.71 | - | 8024 B |
| NinoSerializeInventory | 40.58 ns | 1.00 | 8,004 B | - |
| MemoryPackSerializeInventory | 43.50 ns | 1.07 | 8,004 B | - |
| MessagePackSerializeInventory | 4.80 μs | 118.29 | 5,619 B | - |
| ProtobufSerializeInventory | 26.29 μs | 648.08 | 7,518 B | - |
| NinoDeserializeShapes | 6.29 μs | 1.00 | - | 36056 B |
| MemoryPackDeserializeShapes | 8.70 μs | 1.38 | - | 36056 B |
| MessagePackDeserializeShapes | 32.15 μs | 5.11 | - | 36056 B |
| ProtobufDeserializeShapes | 103.45 μs | 16.45 | - | 36089 B |
| NinoSerializeShapes | 1.03 μs | 1.00 | 14,004 B | - |
| MemoryPackSerializeShapes | 12.17 μs | 11.81 | 12,004 B | - |
| MessagePackSerializeShapes | 19.78 μs | 19.20 | 13,119 B | - |
| ProtobufSerializeShapes | 69.82 μs | 67.79 | 15,365 B | - |
| NinoDeserializeStringDictionary | 20.37 μs | 1.01 | - | 110216 B |
| MemoryPackDeserializeStringDictionary | 32.46 μs | 1.61 | - | 110216 B |
| MessagePackDeserializeStringDictionary | 40.44 μs | 2.00 | - | 110216 B |
| ProtobufDeserializeStringDictionary | 77.11 μs | 3.82 | - | 181416 B |
| NinoSerializeStringDictionary | 4.97 μs | 1.00 | 16,734 B | - |
| MemoryPackSerializeStringDictionary | 20.58 μs | 4.14 | 30,734 B | - |
| MessagePackSerializeStringDictionary | 16.59 μs | 3.34 | 16,733 B | - |
| ProtobufSerializeStringDictionary | 35.94 μs | 7.23 | 20,730 B | 40 B |
| NinoDeserializeTree | 29.95 μs | 1.00 | - | 171872 B |
| MemoryPackDeserializeTree | 36.54 μs | 1.22 | - | 171872 B |
| MessagePackDeserializeTree | 83.87 μs | 2.80 | - | 171872 B |
| NinoSerializeTree | 6.95 μs | 1.00 | 27,163 B | - |
| MemoryPackSerializeTree | 11.98 μs | 1.73 | 32,623 B | - |
| MessagePackSerializeTree | 45.30 μs | 6.52 | 20,338 B | - |
| NinoDeserializeWaypoints | 547.47 ns | 1.00 | - | 24024 B |
| MemoryPackDeserializeWaypoints | 640.25 ns | 1.17 | - | 24024 B |
| MessagePackDeserializeWaypoints | 39.46 μs | 72.16 | - | - |
| ProtobufDeserializeWaypoints | 64.28 μs | 117.53 | - | 24024 B |
| NinoSerializeWaypoints | 165.75 ns | 1.00 | 15,004 B | - |
| MemoryPackSerializeWaypoints | 147.06 ns | 0.89 | 24,004 B | - |
| MessagePackSerializeWaypoints | 21.94 μs | 132.61 | 16,979 B | - |
| ProtobufSerializeWaypoints | 63.09 μs | 381.42 | 19,482 B | - |
PolymorphicTest
| Method | Mean | Ratio | Payload | Allocated |
|---|---|---|---|---|
| NinoDeserializePolymorphicClassArray | 164.54 μs | 1.00 | - | 745624 B |
| MemoryPackDeserializePolymorphicClassArray | 302.23 μs | 1.84 | - | 745624 B |
| MessagePackDeserializePolymorphicClassArray | 1.47 ms | 8.95 | - | 1162424 B |
| ProtobufDeserializePolymorphicClassArray | 5.25 ms | 31.92 | - | 1211492 B |
| NinoSerializePolymorphicClassArray | 39.09 μs | 1.00 | 397,304 B | - |
| MemoryPackSerializePolymorphicClassArray | 251.18 μs | 6.43 | 384,404 B | - |
| MessagePackSerializePolymorphicClassArray | 749.89 μs | 19.19 | 487,103 B | - |
| ProtobufSerializePolymorphicClassArray | 3.76 ms | 96.22 | 635,807 B | 8000 B |
| NinoDeserializePolymorphicClass | 14.17 μs | 1.00 | - | 79688 B |
| MemoryPackDeserializePolymorphicClass | 28.20 μs | 1.99 | - | 79688 B |
| MessagePackDeserializePolymorphicClass | 195.61 μs | 13.83 | - | - |
| ProtobufDeserializePolymorphicClass | 780.20 μs | 55.18 | - | 84377 B |
| NinoSerializePolymorphicClass | 4.61 μs | 1.00 | 53,789 B | - |
| MemoryPackSerializePolymorphicClass | 24.17 μs | 5.24 | 51,760 B | - |
| MessagePackSerializePolymorphicClass | 112.65 μs | 24.43 | 66,859 B | - |
| ProtobufSerializePolymorphicClass | 640.79 μs | 138.98 | 91,495 B | 80 B |
| NinoDeserializePolymorphicFooArray | 13.42 μs | 1.00 | - | 76024 B |
| MemoryPackDeserializePolymorphicFooArray | 29.68 μs | 2.21 | - | 76024 B |
| MessagePackDeserializePolymorphicFooArray | 189.84 μs | 14.16 | - | - |
| ProtobufDeserializePolymorphicFooArray | 771.16 μs | 57.53 | - | 76052 B |
| NinoSerializePolymorphicFooArray | 4.83 μs | 1.00 | 52,404 B | - |
| MemoryPackSerializePolymorphicFooArray | 30.30 μs | 6.27 | 50,404 B | - |
| MessagePackSerializePolymorphicFooArray | 101.02 μs | 20.91 | 65,203 B | - |
| ProtobufSerializePolymorphicFooArray | 648.85 μs | 134.28 | 89,500 B | - |
VersionTolerantTest
| Method | Mean | Ratio | Payload | Allocated |
|---|---|---|---|---|
| NinoDeserializeTolerantClass | 370.89 ns | 1.00 | - | 2288 B |
| MemoryPackDeserializeTolerantClass | 823.27 ns | 2.22 | - | 2528 B |
| MessagePackDeserializeTolerantClass | 1.86 μs | 5.01 | - | - |
| ProtobufDeserializeTolerantClass | 6.20 μs | 16.74 | - | - |
| NinoSerializeTolerantClass | 101.14 ns | 1.00 | 1,073 B | - |
| MemoryPackSerializeTolerantClass | 515.47 ns | 5.10 | 1,164 B | - |
| MessagePackSerializeTolerantClass | 1.12 μs | 11.11 | 1,068 B | - |
| ProtobufSerializeTolerantClass | 3.56 μs | 35.19 | 1,253 B | 80 B |
| NinoDeserializeTolerantClasses | 42.22 μs | 1.00 | - | 229624 B |
| MemoryPackDeserializeTolerantClasses | 84.57 μs | 2.01 | - | 253624 B |
| MessagePackDeserializeTolerantClasses | 197.67 μs | 4.69 | - | 253624 B |
| ProtobufDeserializeTolerantClasses | 595.37 μs | 14.12 | - | - |
| NinoSerializeTolerantClasses | 10.78 μs | 1.00 | 107,304 B | - |
| MemoryPackSerializeTolerantClasses | 50.40 μs | 4.68 | 116,404 B | - |
| MessagePackSerializeTolerantClasses | 114.03 μs | 10.58 | 106,801 B | - |
| ProtobufSerializeTolerantClasses | 351.91 μs | 32.65 | 124,968 B | 8000 B |
CollectionsTest
| Method | Mean | Ratio | Payload | Allocated |
|---|---|---|---|---|
| NinoDeserializeHashSet | 2.09 μs | 1.00 | - | 17768 B |
| MemoryPackDeserializeHashSet | 6.36 μs | 3.05 | - | 17768 B |
| MessagePackDeserializeHashSet | 12.63 μs | 6.05 | - | 58664 B |
| ProtobufDeserializeHashSet | 25.26 μs | 12.09 | - | 58738 B |
| NinoSerializeHashSet | 120.47 ns | 1.00 | 4,004 B | - |
| MemoryPackSerializeHashSet | 6.39 μs | 53.02 | 4,004 B | - |
| MessagePackSerializeHashSet | 4.98 μs | 41.34 | 2,874 B | - |
| ProtobufSerializeHashSet | 12.39 μs | 102.87 | 2,957 B | 40 B |
| NinoDeserializeIntDictionary | 2.39 μs | 1.00 | - | 22192 B |
| MemoryPackDeserializeIntDictionary | 6.46 μs | 2.70 | - | 22192 B |
| MessagePackDeserializeIntDictionary | 11.47 μs | 4.79 | - | 22192 B |
| ProtobufDeserializeIntDictionary | 59.34 μs | 24.78 | - | - |
| NinoSerializeIntDictionary | 133.41 ns | 1.00 | 8,004 B | - |
| MemoryPackSerializeIntDictionary | 2.52 μs | 18.92 | 8,004 B | - |
| MessagePackSerializeIntDictionary | 6.72 μs | 50.36 | 5,818 B | - |
| ProtobufSerializeIntDictionary | 32.30 μs | 242.12 | 7,934 B | 40 B |
| NinoDeserializeKeyValuePairs | 305.32 ns | 1.00 | - | 8024 B |
| MemoryPackDeserializeKeyValuePairs | 335.98 ns | 1.10 | - | 8024 B |
| MessagePackDeserializeKeyValuePairs | 24.09 μs | 78.89 | - | - |
| ProtobufDeserializeKeyValuePairs | 44.73 μs | 146.51 | - | 8024 B |
| NinoSerializeKeyValuePairs | 87.68 ns | 1.00 | 8,004 B | - |
| MemoryPackSerializeKeyValuePairs | 88.41 ns | 1.01 | 8,004 B | - |
| MessagePackSerializeKeyValuePairs | 16.47 μs | 187.87 | 6,563 B | - |
| ProtobufSerializeKeyValuePairs | 35.28 μs | 402.33 | 7,853 B | - |
| NinoDeserializeQueue | 195.73 ns | 1.00 | - | 4064 B |
| MemoryPackDeserializeQueue | 3.05 μs | 15.58 | - | - |
| MessagePackDeserializeQueue | 5.31 μs | 27.11 | - | 4064 B |
| ProtobufDeserializeQueue | 17.00 μs | 86.85 | - | - |
| NinoSerializeQueue | 46.80 ns | 1.00 | 4,004 B | - |
| MemoryPackSerializeQueue | 6.21 μs | 132.75 | 4,004 B | - |
| MessagePackSerializeQueue | 4.50 μs | 96.10 | 2,619 B | - |
| ProtobufSerializeQueue | 10.31 μs | 220.34 | 2,872 B | - |
| NinoDeserializeTupleList | 780.57 ns | 1.00 | - | 16056 B |
| MemoryPackDeserializeTupleList | 861.26 ns | 1.10 | - | 16056 B |
| MessagePackDeserializeTupleList | 25.16 μs | 32.23 | - | 16057 B |
| ProtobufDeserializeTupleList | 39.30 μs | 50.35 | - | 16089 B |
| NinoSerializeTupleList | 169.70 ns | 1.00 | 12,004 B | - |
| MemoryPackSerializeTupleList | 221.03 ns | 1.30 | 16,004 B | - |
| MessagePackSerializeTupleList | 16.89 μs | 99.69 | 6,605 B | - |
| ProtobufSerializeTupleList | 37.89 μs | 223.61 | 8,338 B | - |
| NinoDeserializeConcurrentDictionary | 13.76 μs | 1.00 | - | 49112 B |
| MemoryPackDeserializeConcurrentDictionary | 59.60 μs | 4.33 | - | 141032 B |
| MessagePackDeserializeConcurrentDictionary | 67.33 μs | 4.89 | - | 141032 B |
| ProtobufDeserializeConcurrentDictionary | 116.61 μs | 8.48 | - | 141035 B |
| NinoSerializeConcurrentDictionary | 2.38 μs | 1.00 | 8,004 B | - |
| MemoryPackSerializeConcurrentDictionary | 3.88 μs | 1.63 | 8,004 B | - |
| MessagePackSerializeConcurrentDictionary | 10.57 μs | 4.43 | 5,563 B | 56 B |
| ProtobufSerializeConcurrentDictionary | 47.41 μs | 19.90 | 7,849 B | 96 B |
| NinoDeserializeLinkedList | 11.09 μs | 1.00 | - | 48040 B |
| MemoryPackDeserializeLinkedList | 14.37 μs | 1.30 | - | 48040 B |
| MessagePackDeserializeLinkedList | 18.40 μs | 1.66 | - | 48040 B |
| ProtobufDeserializeLinkedList | 32.01 μs | 2.89 | - | 48041 B |
| NinoSerializeLinkedList | 1.56 μs | 1.00 | 4,004 B | - |
| MemoryPackSerializeLinkedList | 7.91 μs | 5.06 | 4,004 B | - |
| MessagePackSerializeLinkedList | 3.66 μs | 2.34 | 2,619 B | - |
| ProtobufSerializeLinkedList | 17.89 μs | 11.45 | 2,872 B | 48 B |
| NinoDeserializeSelfTree | 99.24 μs | 1.00 | - | 251192 B |
| MemoryPackDeserializeSelfTree | 156.10 μs | 1.57 | - | 251196 B |
| NinoSerializeSelfTree | 27.32 μs | 1.00 | 21,844 B | - |
| MemoryPackSerializeSelfTree | 117.23 μs | 4.29 | 21,844 B | 54600 B |
| NinoDeserializeSortedDictionary | 17.94 μs | 1.00 | - | 48112 B |
| MemoryPackDeserializeSortedDictionary | 52.72 μs | 2.95 | - | - |
| MessagePackDeserializeSortedDictionary | 63.01 μs | 3.53 | - | 48112 B |
| ProtobufDeserializeSortedDictionary | 132.40 μs | 7.41 | - | 48113 B |
| NinoSerializeSortedDictionary | 3.24 μs | 1.00 | 8,004 B | - |
| MemoryPackSerializeSortedDictionary | 6.69 μs | 2.06 | 8,004 B | 200 B |
| MessagePackSerializeSortedDictionary | 16.84 μs | 5.19 | 5,563 B | 200 B |
| ProtobufSerializeSortedDictionary | 54.43 μs | 16.78 | 7,849 B | 288 B |
| NinoDeserializeSortedList | 1.91 μs | 1.00 | - | 8112 B |
| MemoryPackDeserializeSortedList | 16.31 μs | 8.56 | - | 8112 B |
| MessagePackDeserializeSortedList | 22.83 μs | 11.97 | - | 8112 B |
| ProtobufDeserializeSortedList | 73.72 μs | 38.66 | - | 16848 B |
| NinoSerializeSortedList | 1.88 μs | 1.00 | 8,004 B | - |
| MemoryPackSerializeSortedList | 3.40 μs | 1.81 | 8,004 B | - |
| MessagePackSerializeSortedList | 9.21 μs | 4.90 | 5,563 B | 48 B |
| ProtobufSerializeSortedList | 47.51 μs | 25.29 | 7,849 B | 88 B |
| NinoDeserializeSortedSet | 15.83 μs | 1.00 | - | 40048 B |
| MemoryPackDeserializeSortedSet | 53.92 μs | 3.41 | - | 40048 B |
| MessagePackDeserializeSortedSet | 51.55 μs | 3.26 | - | - |
| ProtobufDeserializeSortedSet | 63.22 μs | 3.99 | - | - |
| NinoSerializeSortedSet | 2.84 μs | 1.00 | 4,004 B | - |
| MemoryPackSerializeSortedSet | 11.15 μs | 3.93 | 4,004 B | 200 B |
| MessagePackSerializeSortedSet | 11.99 μs | 4.23 | 2,947 B | 248 B |
| ProtobufSerializeSortedSet | 22.21 μs | 7.83 | 2,981 B | 248 B |
The cases ran in parallel on several runners (every serializer's run of a case on one):
- PooledTest, ReferenceTest, SimpleTest: AMD EPYC 7763 2.72GHz, 1 CPU, 4 logical and 2 physical cores
- FixedSizeTest, PooledGraphTest, ScatteredTest, UntrustedTest: AMD EPYC 7763 3.23GHz, 1 CPU, 4 logical and 2 physical cores
- BclTypesTest, GenericPolymorphicTest: AMD EPYC 7763 2.61GHz, 1 CPU, 4 logical and 2 physical cores
- CommonTypesTest, PolymorphicTest: AMD EPYC 9V74 3.60GHz, 1 CPU, 4 logical and 2 physical cores
- BclTypesTest, CommonTypesTest: AMD EPYC 9V45 4.21GHz, 1 CPU, 4 logical and 2 physical cores
- BclTypesTest (NinoDeserializeBlockingCollection to ProtobufSerializeImmutableStack): AMD EPYC 7763 3.24GHz, 1 CPU, 4 logical and 2 physical cores
- BclTypesTest (NinoDeserializeImmutableSortedDictionary to MessagePackSerializeVersion): INTEL XEON PLATINUM 8573C 3.09GHz, 1 CPU, 4 logical and 2 physical cores
- SimpleTest, VersionTolerantTest: AMD EPYC 7763 2.59GHz, 1 CPU, 4 logical and 2 physical cores
- BclTypesTest, CollectionsTest: AMD EPYC 9V74 3.69GHz, 1 CPU, 4 logical and 2 physical cores
- CollectionsTest (NinoDeserializeConcurrentDictionary to ProtobufSerializeSortedSet): AMD EPYC 7763 3.22GHz, 1 CPU, 4 logical and 2 physical cores
Median, Min, Max and the GC counts: benchmark-x64-full.md.
Benchmark generated automatically on 2026-11-04 16:09:33 UTC
🌐 TypeScript runtime benchmark
Generated TypeScript of v4.0.0-rc.8 against Protocol Buffers, MessagePack and JSON, in Node
The code nino-gen generates for runtimes/typescript/bench (Program.cs's types), against Protocol Buffers (protobuf-es), MessagePack (msgpackr) and JSON (to and from UTF-8 bytes), in Node v27.0.0-alpha.0.0.0, on data .NET wrote, timed by tinybench 6.2.2: the mean time of a pass over each scenario's values (each pass the next of 64 variants, so that no pass reads what the one before read), ± its relative margin of error, and passes per second.
| Scenario | Operation | Nino | Protobuf | MessagePack | JSON |
|---|---|---|---|---|---|
| Player x1000 | serialize | 273.33 μs ±0.5% (3,659/s) | 2.96 ms ±6% (337/s) | 2.11 ms ±0.7% (473/s) | 1.77 ms ±5% (565/s) |
| Player x1000 | serializeInto | 172.38 μs ±0.3% (5,801/s) | |||
| Player x1000 | deserialize | 719.03 μs ±3% (1,391/s) | 3.24 ms ±8% (309/s) | 1.68 ms ±0.4% (597/s) | 1.84 ms ±0.3% (544/s) |
| Player x1000 | size | 118.3 KiB | 100.2 KiB | 187.9 KiB | 285.5 KiB |
| Zoo 50 x 100 animals (type ids) | serialize | 195.63 μs ±0.5% (5,112/s) | 1.32 ms ±2% (757/s) | 1.88 ms ±0.7% (532/s) | 473.60 μs ±0.7% (2,112/s) |
| Zoo 50 x 100 animals (type ids) | serializeInto | 180.87 μs ±0.4% (5,529/s) | |||
| Zoo 50 x 100 animals (type ids) | deserialize | 154.10 μs ±0.4% (6,489/s) | 3.06 ms ±6% (326/s) | 1.14 ms ±2% (877/s) | 1.76 ms ±1.0% (568/s) |
| Zoo 50 x 100 animals (type ids) | size | 71.4 KiB | 71.3 KiB | 117.5 KiB | 161.2 KiB |
| OrderBook 100 x 50 orders | serialize | 450.29 μs ±0.3% (2,221/s) | 2.64 ms ±4% (379/s) | 3.38 ms ±0.8% (296/s) | 1.20 ms ±0.7% (832/s) |
| OrderBook 100 x 50 orders | serializeInto | 416.93 μs ±0.2% (2,398/s) | |||
| OrderBook 100 x 50 orders | deserialize | 446.26 μs ±0.7% (2,241/s) | 4.50 ms ±5% (222/s) | 3.20 ms ±1% (313/s) | 1.54 ms ±0.5% (649/s) |
| OrderBook 100 x 50 orders | size | 225.1 KiB | 235.9 KiB | 433.9 KiB | 551.3 KiB |
| Article x200 (text) | serialize | 217.25 μs ±0.6% (4,603/s) | 1.85 ms ±4% (541/s) | 422.82 μs ±0.5% (2,365/s) | 895.56 μs ±3% (1,117/s) |
| Article x200 (text) | serializeInto | 143.70 μs ±0.2% (6,959/s) | |||
| Article x200 (text) | deserialize | 189.22 μs ±0.7% (5,285/s) | 791.97 μs ±7% (1,263/s) | 468.33 μs ±0.4% (2,135/s) | 789.73 μs ±0.6% (1,266/s) |
| Article x200 (text) | size | 430.2 KiB | 421.6 KiB | 428.2 KiB | 434.2 KiB |
| Mesh 20 x (2 x 500 Vec3, 1500 int) | serialize | 245.79 μs ±0.6% (4,069/s) | 3.87 ms ±1% (258/s) | 7.47 ms ±0.5% (134/s) | 4.26 ms ±0.5% (235/s) |
| Mesh 20 x (2 x 500 Vec3, 1500 int) | serializeInto | 201.08 μs ±0.3% (4,973/s) | |||
| Mesh 20 x (2 x 500 Vec3, 1500 int) | deserialize | 337.36 μs ±6% (2,964/s) | 7.47 ms ±7% (134/s) | 3.23 ms ±1% (310/s) | 5.22 ms ±0.9% (191/s) |
| Mesh 20 x (2 x 500 Vec3, 1500 int) | size | 351.9 KiB | 383.3 KiB | 769.5 KiB | 1514.3 KiB |
| Samples 20 x (3 x 1000 numbers) | serialize | 128.06 μs ±0.9% (7,809/s) | 1.20 ms ±0.9% (831/s) | 944.94 μs ±0.5% (1,058/s) | 2.07 ms ±0.6% (483/s) |
| Samples 20 x (3 x 1000 numbers) | serializeInto | 96.55 μs ±0.1% (10,357/s) | |||
| Samples 20 x (3 x 1000 numbers) | deserialize | 184.79 μs ±1% (5,411/s) | 765.46 μs ±0.8% (1,306/s) | 884.34 μs ±0.8% (1,131/s) | 1.93 ms ±0.6% (518/s) |
| Samples 20 x (3 x 1000 numbers) | size | 312.8 KiB | 293.0 KiB | 448.0 KiB | 876.1 KiB |
| Lookup 50 x (2 maps of 100) | serialize | 356.19 μs ±0.3% (2,807/s) | 1.99 ms ±2% (503/s) | 690.45 μs ±0.4% (1,448/s) | 1.24 ms ±2% (810/s) |
| Lookup 50 x (2 maps of 100) | serializeInto | 331.63 μs ±0.1% (3,015/s) | |||
| Lookup 50 x (2 maps of 100) | deserialize | 1.04 ms ±1% (962/s) | 4.20 ms ±4% (238/s) | 2.06 ms ±2% (484/s) | 6.14 ms ±12% (163/s) |
| Lookup 50 x (2 maps of 100) | size | 156.2 KiB | 178.4 KiB | 147.7 KiB | 194.5 KiB |
| Event x1000 (long) | serialize | 80.24 μs ±0.6% (12,463/s) | 1.35 ms ±4% (740/s) | 911.73 μs ±0.3% (1,097/s) | 1.73 ms ±5% (579/s) |
| Event x1000 (long) | serializeInto | 41.71 μs ±0.4% (23,976/s) | |||
| Event x1000 (long) | deserialize | 66.87 μs ±0.8% (14,955/s) | 937.68 μs ±10% (1,066/s) | 273.14 μs ±0.5% (3,661/s) | 800.90 μs ±0.6% (1,249/s) |
| Event x1000 (long) | size | 55.7 KiB | 50.6 KiB | 77.1 KiB | 151.9 KiB |
| Strings 1000 (ASCII) | serialize | 27.07 μs ±0.7% (36,945/s) | 50.97 μs ±0.4% (19,621/s) | 44.13 μs ±0.2% (22,659/s) | 13.05 μs ±0.4% (76,599/s) |
| Strings 1000 (ASCII) | serializeInto | 25.89 μs ±0.1% (38,620/s) | |||
| Strings 1000 (ASCII) | deserialize | 17.93 μs ±0.5% (55,781/s) | 109.87 μs ±0.5% (9,102/s) | 26.96 μs ±0.5% (37,098/s) | 30.47 μs ±0.4% (32,818/s) |
| Strings 1000 (ASCII) | size | 10.6 KiB | 11.6 KiB | 10.6 KiB | 12.6 KiB |
| Strings 1000 (ASCII, Latin-1, other) | serialize | 38.97 μs ±0.3% (25,661/s) | 133.09 μs ±0.3% (7,514/s) | 65.94 μs ±0.2% (15,165/s) | 32.71 μs ±0.3% (30,567/s) |
| Strings 1000 (ASCII, Latin-1, other) | serializeInto | 36.89 μs ±0.1% (27,107/s) | |||
| Strings 1000 (ASCII, Latin-1, other) | deserialize | 34.80 μs ±0.3% (28,738/s) | 298.82 μs ±0.3% (3,347/s) | 169.86 μs ±0.2% (5,887/s) | 50.47 μs ±0.3% (19,814/s) |
| Strings 1000 (ASCII, Latin-1, other) | size | 20.4 KiB | 21.4 KiB | 20.4 KiB | 22.4 KiB |
| IntSet 20 x 1000 | serialize | 86.11 μs ±0.4% (11,612/s) | 594.32 μs ±1% (1,683/s) | 331.15 μs ±0.3% (3,020/s) | 554.09 μs ±0.4% (1,805/s) |
| IntSet 20 x 1000 | serializeInto | 74.84 μs ±0.1% (13,362/s) | |||
| IntSet 20 x 1000 | deserialize | 696.00 μs ±0.3% (1,437/s) | 985.55 μs ±0.4% (1,015/s) | 841.54 μs ±0.6% (1,188/s) | 1.17 ms ±0.3% (852/s) |
| IntSet 20 x 1000 | size | 78.2 KiB | 95.3 KiB | 97.9 KiB | 205.0 KiB |
| StringSet 20 x 1000 | serialize | 514.01 μs ±0.4% (1,945/s) | 1.04 ms ±0.9% (962/s) | 1.00 ms ±0.3% (998/s) | 301.14 μs ±0.9% (3,321/s) |
| StringSet 20 x 1000 | serializeInto | 489.57 μs ±0.2% (2,043/s) | |||
| StringSet 20 x 1000 | deserialize | 4.55 ms ±3% (220/s) | 7.33 ms ±2% (136/s) | 4.84 ms ±3% (207/s) | 5.05 ms ±3% (198/s) |
| StringSet 20 x 1000 | size | 212.8 KiB | 232.2 KiB | 212.9 KiB | 252.0 KiB |
🐹 Go runtime benchmark
The Go runtime of v4.0.0-rc.8 against Protocol Buffers, MessagePack, encoding/json and gob
runtimes/go/bench (testing.B, summarized by benchstat: medians ± their 95% confidence interval): codecs in the shape nino-gen generates (Nino) and the struct tags (NinoTags) against Protocol Buffers (google.golang.org/protobuf), MessagePack (vmihailenco/msgpack), encoding/json and gob, on Intel(R) Xeon(R) Platinum 8370C CPU @ 2.80GHz, linux/amd64. Allocations are counted per operation; sizes are the encoded bytes.
| Case | Operation | Nino | NinoTags | Protobuf | MessagePack | JSON | Gob |
|---|---|---|---|---|---|---|---|
| simple-class | serialize | 467.10 ns ±3% | 974.95 ns ±1% | 4.74 μs ±1% | 6.14 μs ±2% | 9.16 μs ±1% | 8.63 μs ±2% |
| simple-class | deserialize | 1.37 μs ±6% | 1.88 μs ±1% | 6.76 μs ±1% | 11.56 μs ±1% | 16.41 μs ±3% | 24.55 μs ±3% |
| simple-class | serialize allocs | 0 | 1 | 80 | 41 | 15 | 65 |
| simple-class | deserialize allocs | 12 | 16 | 66 | 64 | 29 | 254 |
| simple-class | size | 1.0 KiB | 1.0 KiB | 1.1 KiB | 1.2 KiB | 1.9 KiB | 1.3 KiB |
| simple-class-100 | serialize | 47.02 μs ±1% | 95.95 μs ±1% | 485.01 μs ±3% | 616.08 μs ±0% | 953.15 μs ±2% | 439.60 μs ±1% |
| simple-class-100 | deserialize | 137.32 μs ±1% | 171.67 μs ±4% | 688.44 μs ±1% | 1.15 ms ±2% | 1.69 ms ±2% | 546.82 μs ±3% |
| simple-class-100 | serialize allocs | 0 | 101 | 8000 | 4101 | 1403 | 4042 |
| simple-class-100 | deserialize allocs | 1204 | 1407 | 6609 | 6208 | 2839 | 2749 |
| simple-class-100 | size | 104.4 KiB | 104.4 KiB | 111.4 KiB | 122.9 KiB | 193.1 KiB | 107.1 KiB |
| scattered-1m | serialize | 140.05 ms ±5% | 277.03 ms ±4% | 383.70 ms ±6% | 512.53 ms ±7% | 757.49 ms ±7% | 454.93 ms ±5% |
| scattered-1m | deserialize | 84.49 ms ±8% | 157.92 ms ±7% | 257.09 ms ±7% | 506.83 ms ±4% | 822.84 ms ±1% | 269.23 ms ±10% |
| scattered-1m | serialize allocs | 0 | 1 | 0 | 1 | 32 | 64 |
| scattered-1m | deserialize allocs | 1017237 | 1017241 | 3000039 | 3000011 | 3000044 | 3000187 |
| scattered-1m | size | 48611.1 KiB | 48611.1 KiB | 52501.2 KiB | 70095.5 KiB | 92173.2 KiB | 52879.2 KiB |
| int-set-1000 | serialize | 69.29 ns ±3% | 146.25 ns ±0% | 7.53 μs ±1% | 29.66 μs ±2% | 29.53 μs ±3% | 15.64 μs ±3% |
| int-set-1000 | deserialize | 3.82 μs ±3% | 1.42 μs ±2% | 9.83 μs ±2% | 44.57 μs ±1% | 69.70 μs ±1% | 22.05 μs ±2% |
| int-set-1000 | serialize allocs | 0 | 1 | 0 | 1 | 3 | 24 |
| int-set-1000 | deserialize allocs | 2 | 5 | 2 | 8 | 13 | 154 |
| int-set-1000 | size | 3.9 KiB | 3.9 KiB | 4.8 KiB | 4.9 KiB | 10.2 KiB | 4.9 KiB |
| string-set-1000 | serialize | 2.39 μs ±4% | 12.44 μs ±0% | 14.85 μs ±2% | 35.32 μs ±1% | 46.18 μs ±1% | 34.42 μs ±1% |
| string-set-1000 | deserialize | 33.38 μs ±2% | 56.98 μs ±2% | 43.91 μs ±4% | 129.49 μs ±3% | 151.28 μs ±2% | 95.36 μs ±2% |
| string-set-1000 | serialize allocs | 0 | 1 | 0 | 1 | 3 | 27 |
| string-set-1000 | deserialize allocs | 38 | 1010 | 1012 | 2008 | 2013 | 2154 |
| string-set-1000 | size | 10.6 KiB | 10.6 KiB | 11.6 KiB | 10.6 KiB | 12.6 KiB | 10.7 KiB |
| strings-1000 | serialize | 2.31 μs ±3% | 2.47 μs ±5% | 13.70 μs ±2% | 11.28 μs ±2% | 32.89 μs ±1% | 22.35 μs ±3% |
| strings-1000 | deserialize | 12.58 μs ±2% | 12.84 μs ±3% | 41.61 μs ±1% | 43.46 μs ±2% | 90.48 μs ±3% | 46.06 μs ±3% |
| strings-1000 | serialize allocs | 0 | 1 | 0 | 1 | 3 | 27 |
| strings-1000 | deserialize allocs | 7 | 9 | 1012 | 1004 | 1013 | 1153 |
| strings-1000 | size | 10.6 KiB | 10.6 KiB | 11.6 KiB | 10.6 KiB | 12.6 KiB | 10.7 KiB |
| strings-mixed-1000 | serialize | 8.41 μs ±0% | 8.51 μs ±1% | 28.14 μs ±1% | 11.23 μs ±1% | 60.63 μs ±0% | 34.73 μs ±2% |
| strings-mixed-1000 | deserialize | 27.30 μs ±3% | 27.60 μs ±3% | 60.20 μs ±3% | 46.29 μs ±5% | 157.48 μs ±3% | 47.15 μs ±1% |
| strings-mixed-1000 | serialize allocs | 0 | 1 | 0 | 1 | 3 | 29 |
| strings-mixed-1000 | deserialize allocs | 12 | 14 | 1012 | 1004 | 1013 | 1153 |
| strings-mixed-1000 | size | 20.4 KiB | 20.4 KiB | 21.4 KiB | 20.4 KiB | 22.3 KiB | 20.4 KiB |
| strings-mixed-utf8-1000 | serialize | 14.48 μs ±1% | |||||
| strings-mixed-utf8-1000 | deserialize | 28.59 μs ±11% | |||||
| strings-mixed-utf8-1000 | serialize allocs | 0 | |||||
| strings-mixed-utf8-1000 | deserialize allocs | 12 | |||||
| strings-mixed-utf8-1000 | size | 21.4 KiB |