neuron-explorer
Steering-first neuron explorer for Qwen3.5-4B: push every MLP neuron, keep every rollout, browse the results
Nino is a general-purpose, high-performance binary serializer for C# and .NET: source-generated, up to 6.83× faster than MemoryPack, zero GC on hot paths. For RPC, microservices, message queues, cachi
Developer toolsC# · RustGitHub @JasonXuDeveloperOnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
The fastest binary serializer for C#. Source-generated, zero-allocation: services, RPC, messaging, caching, storage, Unity and Godot.
Taken from the repository README
Loading the full README.
Read the full README ↗The fastest binary serializer for C#. General-purpose, source-generated, zero-allocation and thread-safe: for services, RPC, messaging, caching and storage on .NET, with Unity, Godot and HybridCLR support.
🌐 Website · 🚀 Getting Started · What's new in v4 · Benchmarks · vs MemoryPack · 中文
Up to 6.8× faster serialization and up to 8.7× faster in-place deserialization than MemoryPack Zero GC allocations on hot paths · 221× faster builds than v3
[!IMPORTANT] Nino is built to read untrusted data: messages from network peers, files users hand you, request bodies. Crafted bytes cannot crash the process, read past the input, allocate out of proportion to their size (a collection allocates at most 16× the bytes left ahead of its elements) or name a type to create: the data holds type ids of your declared subtypes, never type names. Every invalid input throws one exception,
NinoDataException(anInvalidOperationException, message startingNino:), so a singlecatchat the boundary handles it. Keys built to collide in a hash table throw too (NinoSettings.MaxHashCollisions), andNinoSettings.MaxDepthandMaxCollectionLengthtighten the limits. For data from outside, setNinoSettings.Untrusted = trueat startup: the dictionaries and sets Nino creates hash integer,char,Guidandstringkeys with a per-process secret,bool,DateTime,decimal,DateOnlyandTimeOnlyvalues are checked even inside raw structs, and bytes after the value are rejected; with it off (the default) none of this costs anything. Fuzzed and hand-crafted payloads were run against every built-in type, and what they found is fixed in 4.0.0-rc.7. What Nino checks, and what stays your code's job. (MemoryPack now recommends itself for trusted data only; see the comparison.)
BenchmarkDotNet 0.15.8, .NET 10.0.13, server GC, INTEL XEON PLATINUM 8573C 3.50 GHz; Nino v4 and MemoryPack 1.21.4 in one run, Nino 3.3.13 from NuGet directly before it on the same machine. Payloads are about the same size (SimpleClass: 1,104 bytes in v4, 1,152 in v3, 1,148 in MemoryPack). Every case and the method: docs/v4-benchmarks.md; the notes of each release carry that release's numbers.
NinoDataException.
Untrusted data.IBufferWriter<byte>) allocates nothing. Deserializing into existing or pooled objects
([NinoPooled], reuse deserialization into existing objects) allocates nothing either (a string whose text is unchanged is kept),
and is up to 8.7× faster than MemoryPack's overwrite mode.netstandard2.0, netstandard2.1, net8.0 and net10.0 write identical bytes on
CoreCLR, Mono and IL2CPP, 64-bit and 32-bit, x64 and Arm64, so a Unity client and a .NET server talk to each other directly.BigInteger, Uri, Version,
, , and more, every immutable collection (), Unity's ,
and , your own collection types, and anything else through a pluggable formatter.
.| ⚙️ Services & IPC | Compact binary payloads, no per-call allocations, and safe concurrent use from ASP.NET Core, workers and actors. Nino.AspNetCore reads and writes application/x-nino bodies in MVC and Minimal APIs. |
| 🧩 Web, Node, Go & Rust | Nino.Codegen generates TypeScript, Go or Rust types and codecs for your [NinoType]s after each build, so browsers, Node, Go and Rust read and write the same bytes as .NET. |
| 🌐 RPC & networking | Serialize straight into an IBufferWriter<byte> (a PipeWriter, a pooled buffer), deserialize from a ReadOnlySpan<byte>, and reuse pooled message objects. Polymorphic messages need no tags, and a crafted message throws one exception instead of taking the server down. |
| 📨 Message queues & event streams | Polymorphic event types behind one base type, small payloads, and formats that let producers and consumers upgrade at different times. |
| 🗄️ Caching | Values for Redis, IDistributedCache/HybridCache or in-process caches, with schema changes across deployments. |
| 💾 Persistence & storage | Version tolerance and keyed members let you add, reorder and remove fields across releases. Reference tracking keeps object graphs with shared references and cycles. |
Nino moves arrays of tuples and structs, and converts strings, with the vector instructions each platform has. Every path writes the same bytes as the portable code, which takes over where a CPU or runtime has none. SIMD by platform.
| Platform | Vector paths | Benchmark |
|---|---|---|
| .NET 8 and 10 | x64: AVX-512 (byte permutes, 64-byte copies), AVX2, SSSE3; Arm64: AdvSimd (TBL lookups, paired loads and stores); WebAssembly (.NET 10): PackedSimd swizzles; portable code elsewhere | .NET |
| Unity (Mono, IL2CPP) | With com.unity.burst: Burst-compiled kernels, AVX2 (blocks of 256 values or more) and SSSE3 on x86 and x64, Neon on Arm64; Nino.Core's own paths on Armv7, WebGL and without Burst | Unity |
| .NET Framework, .NET Core 3.0–7 | No intrinsics (the netstandard builds): blocks over 64 bytes through memmove; long Latin-1 text and padded structs with Vector<T> where the JIT accelerates it, else a word at a time | |
| Go | Strings in assembly: AVX2 and AVX-512 conversion on amd64, 16-byte ASCII copies (SSE2 on amd64, NEON on arm64); plain Go elsewhere and with the purego tag | Go |
| Rust | Strings, in its one unsafe module: AVX-512, AVX2, SSE4.1, SSSE3 and SSE2 on x86_64 (found at run time), NEON on Arm64; two u64 words a step elsewhere | Rust |
New to Nino? Follow Getting Started on the website: installation for .NET, Unity and Godot, your first type, and the basics.
dotnet add package Nino --prerelease # .NET, Godot
dotnet add package Nino.Immutable --prerelease # optional: System.Collections.Immutable types
dotnet add package Nino.AspNetCore --prerelease # optional: ASP.NET Core MVC formatters and Minimal API bodies
dotnet add package Nino.Codegen --prerelease # optional: your types as TypeScript, Go or Rust, after each build
dotnet tool install -g Nino.Codegen.Tool --prerelease # optional: the same as the nino-gen command
openupm add com.jasonxudeveloper.nino # Unity (generator and Unity class formatters included)
Nino brings in the runtime (Nino.Serialization) and the source generator (Nino.Generator). Nino v4 is a
prerelease, so --prerelease is required: without it NuGet installs 3.9.x, which cannot read or write v4 data.
npx skills add JasonXuDeveloper/Nino # the `nino-serialization` skill: Claude Code, Codex, Cursor, Copilot and other agents
The skill (skills/nino-serialization/SKILL.md) teaches your assistant to declare types, call the API, pick the fast forms and fix Nino's diagnostics. The docs are also published for models at https://nino.xgamedev.net/llms.txt.
using Nino.Core;
[NinoType]
public class Order
{
public long Id;
public string Customer;
public List<OrderLine> Lines;
}
byte[] bytes = NinoSerializer.Serialize(order);
Order copy = NinoDeserializer.Deserialize<Order>(bytes);
[NinoType] public abstract class Message { public int Seq; }
[NinoType] public sealed class Ping : Message { public long SentAt; }
[NinoType] public sealed class Chat : Message { public string Text; }
byte[] bytes = NinoSerializer.Serialize<Message>(new Chat { Text = "hi" }); // declared as the base: a type id is written
Message msg = NinoDeserializer.Deserialize<Message>(bytes); // a Chat again: the type id picks the subclass
Serialize and deserialize through the common [NinoType] base (not object): generated code dispatches on the type id,
and reuse deserialization and pools work with it. SerializeBoxed/DeserializeBoxed exist only for a type known
only as a Type at run time that shares no [NinoType] base with the others.
// Serialize into the socket pipe: one copy from a pooled per-thread buffer, no byte[] allocated.
NinoSerializer.Serialize(msg, pipeWriter);
// Reuse pooled instances (and their lists) instead of allocating new ones.
[NinoType, NinoPooled]
public sealed partial class ReadingBatch
{
public long Sequence;
public List<SensorReading> Readings;
private static partial IEnumerable<ReadingBatch> OnNinoPoolWarmup() => Enumerable.Range(0, 16).Select(_ => new ReadingBatch());
private partial void OnNinoPoolReturn() { }
}
var batch = NinoDeserializer.Deserialize<ReadingBatch>(span); // taken from the pool
// ... process it ...
batch.ReturnToPool();
[NinoType(format: NinoFormat.Keyed)] // members are matched by name: add, reorder, remove or rename them
public class Profile { [NinoFormerName("Mail")] public string Email; public string Name; }
[NinoType(trackReferences: true)] // shared references and cycles survive a round trip
public class Node { public string Name; public List<Node> Links; }
Read the documentation on nino.xgamedev.net →, including advanced guides for high-performance serialization, networking, ASP.NET Core, TypeScript clients, message queues, caching, persistence and Unity.
Besides speed, the clearest difference is untrusted input. MemoryPack's README now calls it "a high-performance serialization format for trusted data only" and no longer recommends it "where the input can come from an untrusted party, such as public network endpoints or user-supplied files" (MemoryPack README). Nino is built for that input: lengths and allocation are bounded by the bytes received, the stack by a depth limit, the data names no types, and every invalid input throws one exception (details).
| Nino v4 | MemoryPack 1.21.4 | |
|---|---|---|
| Untrusted input | built for it: bounded allocation and stack, one exception type | trusted data only, per its README |
| Serialize classes and polymorphic types | 3.5–6.8× faster | |
| Deserialize classes and polymorphic types | 1.3–1.9× faster | |
| Raw struct arrays | at least on par (a block copy in both); v4 zeroes padding, so declare hot structs without it | copies padding as is |
| Deserialize into existing objects | 0 B, up to 8.7× faster (in place) | overwrite mode, allocates |
| Polymorphism | automatic type ids, any base class or interface, generic bases included | [MemoryPackUnion] tags you maintain, on interfaces and abstract classes, not on generic types |
| Keyed members, reference tracking, object pools | ✅ | version tolerance and circular references, no pools |
| Unity | ✅ 2022.3+, one OpenUPM package, netstandard2.0 and 2.1, HybridCLR without an init call | ✅ 2022.3.12f1+, NuGetForUnity plus a git package, netstandard2.1 |
We round-trip every value through MemoryPack 1.21.4 before timing it. These fail (details):
BigInteger of up to 255 bytes reads back as another value, on every runtime: its formatter writes
temp.Slice(written) of its stack buffer where temp.Slice(0, written) is meant.BitArray on .NET 10 is written wrong, and reading it corrupts the heap and crashes the process: the formatter
reinterprets it with .NET 8's field layout, which .NET 10 changed.(1, 2L) written on .NET reads back as (2, 1) on Mono and in
Unity, because ValueTuple is laid out differently there and MemoryPack copies its memory, padding included.Nino writes all three by their values, not by the runtime's private layout, so they round-trip everywhere.
MemoryPack still has System.Type (left out of Nino on purpose: data that names the types to create is an attack
vector). Nino.Codegen generates TypeScript (and Go and Rust) from the layouts the formatters are generated from, after each build,
structs and version-tolerant types included
(compared with MemoryPack's,
which its README says will be removed in its next release),
and Nino.AspNetCore adds MVC formatters and Minimal API bodies; browser clients can also run the same C# types on
.NET WebAssembly. Streams, async I/O, multi-segment
ReadOnlySequence<byte> input, callbacks and compression are a few lines around Nino's IBufferWriter<byte> and
ReadOnlySpan<byte> APIs; the full comparison has examples.
MemoryPack's union tag is 1 byte where Nino writes a 4-byte type id, and that is a trade: the id is derived from the
type, so subtypes need no tags, can be added in any assembly (a plugin or a HybridCLR hot update) and never renumber,
while every MemoryPack union lists or registers its subtypes by hand.
v4 is a rewrite of the runtime and the generator. Its binary format is not compatible with v3.
| v4 vs v3 (3.3.13) | |
|---|---|
| Serialize | 1.79× faster (class), 2.29× faster (class array), faster (struct, too fast to measure: under 1 ns), 1.13× faster (struct array) |
| Deserialize | 1.21× faster (class), 1.21× faster (class array), on par (struct), 1.07× faster (struct array), while adding reference tracking, reuse deserialization and payload validation |
| Compiling the generated code | 51× faster at 330 types (42.9 s → 0.84 s), 221× faster at 1,100 types (462 s → 2.1 s) |
| Running the generator | 3.1× faster; after an edit, only that type is re-generated (8.3× faster) |
| Generated code | 14× smaller source, 5× smaller IL |
New in v4:
NinoDataException for every invalid input, bytes Nino never writes rejected, a bound on hash collisions,
NinoSettings.MaxDepth, MaxCollectionLength and Untrusted, checked with fuzzed and hand-crafted payloads.[assembly: NinoTypeLayout] metadata, so a change in the bytes shows up between builds.[NinoPooled] pools, [NinoFactory], and
NinoArrayBufferWriter, which can rent from your own ArrayPool<byte>.[assembly: NinoFormatter(...)]), user collection types, BigInteger, Uri,
Version, StringBuilder, BitArray, CultureInfo, TimeZoneInfo, Lazy<T>, Memory<T>, ReadOnlySequence<T>,
read-only and concurrent collections, ILookup, BlockingCollection<T>, PriorityQueue (.NET 8+), every immutable
collection (Nino.Immutable) and Unity's AnimationCurve, Gradient and RectOffset (Nino.UnityTypes, in the
Unity package).KeyValuePairs, and SIMD on AVX-512, AVX2, Arm64 and WebAssembly; releases are benchmarked on x64 and Arm64.AssemblyLoadContext (NinoRegistry.Remove before .NET 8), and
HybridCLR without an init call.[NinoUtf8] writes UTF-8 per member.NinoBody<T> and NinoResults) and Nino.Codegen
(your types and codecs in TypeScript, Go or Rust, generated after each build; or from a built assembly with
Nino.Codegen.Tool's ).See what's new, the changelog and the migration guide; internals are in docs/v4-design.md.
Issues and pull requests are welcome. CONTRIBUTING.md covers building, the test matrix and releasing.
MIT License · Made by JasonXuDeveloper
Memory<T>ILookup<,>Lazy<T>Nino.ImmutableAnimationCurveGradientRectOffsetapplication/x-nino;
Nino.Codegen writes your types and their codecs as TypeScript, Go or Rust after each build (or from a built
assembly with the nino-gen tool, Nino.Codegen.Tool), so browsers, Node, Go services and Rust programs read and
write the same bytes as .NET, with the runtimes in runtimes/ (docs/codegen.md).| 🎮 Unity | Unity 2022.3+, Mono and IL2CPP, HybridCLR hot-update assemblies. Vector3, Quaternion, Color32 and other unmanaged structs are copied as raw memory; AnimationCurve, Gradient and RectOffset are built in. With com.unity.burst installed, arrays of tuples and structs and Latin-1 strings are moved by Burst-compiled SIMD kernels (AVX2, SSSE3, Arm64 Neon). Install from OpenUPM. |
| 👾 Godot 4 (.NET) | A regular NuGet package for Godot's .NET projects. Godot's math structs (Vector3, Transform3D, …) are unmanaged and copied as raw memory. |
nino-gen