Upgrade to Pro — share decks privately, control downloads, hide ads and more …

C#11 による世界最速バイナリシリアライザー「MemoryPack」の作り方

C#11 による世界最速バイナリシリアライザー「MemoryPack」の作り方

.NET Conf 2022 Recap Event Tokyo #dotnetconf

Yoshifumi Kawai

December 06, 2022
Tweet

More Decks by Yoshifumi Kawai

Other Decks in Technology

Transcript

  1. View Slide

  2. 河合 宜文 / Kawai Yoshifumi / @neuecc
    Cysharp, Inc.
    Cygames
    C#大統一理論
    C#

    View Slide

  3. #1 Binary Serializer in .NET
    https://github.com/neuecc/MessagePack-CSharp

    View Slide

  4. Zero encoding extreme fast binary serializer
    https://github.com/Cysharp/MemoryPack/
    究極的

    View Slide

  5. 1リクエストに1000回実行することはある

    View Slide

  6. バッチやクライアントアプリケーションでも

    View Slide

  7. Incremental Source Generator

    View Slide

  8. View Slide

  9. View Slide

  10. No.

    View Slide

  11. View Slide

  12. Serialize/MemoryPackWriter

    View Slide

  13. View Slide

  14. public void WriteUnmanaged(scoped in T1 value1)
    where T1 : unmanaged
    {
    var size = Unsafe.SizeOf();
    ref var spanRef = ref GetSpanReference(size);
    Unsafe.WriteUnaligned(ref spanRef, value1);
    Advance(size);
    }
    書き込み用バッファー管理
    public ref partial struct MemoryPackWriter
    where TBufferWriter : IBufferWriter
    {
    ref TBufferWriter bufferWriter;
    ref byte bufferReference;
    int bufferLength;
    ref byte GetSpanReference(int sizeHint);
    void Advance(int count);
    public MemoryPackWriter(ref TBufferWriter writer)
    }
    public interface System.Buffers.IBufferWriter
    {
    Span GetSpan(int sizeHint = 0);
    void Advance(int count);
    }

    View Slide

  15. public void WriteUnmanaged(scoped in T1 value1)
    where T1 : unmanaged
    {
    var size = Unsafe.SizeOf();
    ref var spanRef = ref GetSpanReference(size);
    Unsafe.WriteUnaligned(ref spanRef, value1);
    Advance(size);
    }
    書き込み用バッファー管理
    public ref partial struct MemoryPackWriter
    where TBufferWriter : IBufferWriter
    {
    ref TBufferWriter bufferWriter;
    ref byte bufferReference;
    int bufferLength;
    ref byte GetSpanReference(int sizeHint);
    void Advance(int count);
    public MemoryPackWriter(ref TBufferWriter writer)
    }
    public interface System.Buffers.IBufferWriter
    {
    Span GetSpan(int sizeHint = 0);
    void Advance(int count);
    }

    View Slide

  16. public void WriteUnmanaged(scoped in T1 value1)
    where T1 : unmanaged
    {
    var size = Unsafe.SizeOf();
    ref var spanRef = ref GetSpanReference(size);
    Unsafe.WriteUnaligned(ref spanRef, value1);
    Advance(size);
    }
    書き込み用バッファー管理
    public ref partial struct MemoryPackWriter
    where TBufferWriter : IBufferWriter
    {
    ref TBufferWriter bufferWriter;
    ref byte bufferReference;
    int bufferLength;
    ref byte GetSpanReference(int sizeHint);
    void Advance(int count);
    public MemoryPackWriter(ref TBufferWriter writer)
    }
    public interface System.Buffers.IBufferWriter
    {
    Span GetSpan(int sizeHint = 0);
    void Advance(int count);
    }

    View Slide

  17. public void WriteUnmanaged(scoped in T1 value1)
    where T1 : unmanaged
    {
    var size = Unsafe.SizeOf();
    ref var spanRef = ref GetSpanReference(size);
    Unsafe.WriteUnaligned(ref spanRef, value1);
    Advance(size);
    }
    書き込み用バッファー管理
    public ref partial struct MemoryPackWriter
    where TBufferWriter : IBufferWriter
    {
    ref TBufferWriter bufferWriter;
    ref byte bufferReference;
    int bufferLength;
    ref byte GetSpanReference(int sizeHint);
    void Advance(int count);
    public MemoryPackWriter(ref TBufferWriter writer)
    }
    public interface System.Buffers.IBufferWriter
    {
    Span GetSpan(int sizeHint = 0);
    void Advance(int count);
    }

    View Slide

  18. 書き込み用バッファー管理
    public ref partial struct MemoryPackWriter
    where TBufferWriter : IBufferWriter
    {
    ref TBufferWriter bufferWriter;
    ref byte bufferReference;
    int bufferLength;
    ref byte GetSpanReference(int sizeHint);
    void Advance(int count);
    public MemoryPackWriter(ref TBufferWriter writer)
    }

    View Slide

  19. public static partial class MemoryPackSerializer
    {
    public static void Serialize(in TBufferWriter bufferWriter, in T? value)
    public static byte[] Serialize(in T? value)
    public static ValueTask SerializeAsync(Stream stream, T? value)
    }
    var writer = new MemoryPackWriter(ref bufferWriter);
    writer.WriteValue(value);
    writer.Flush();

    View Slide

  20. public static partial class MemoryPackSerializer
    {
    public static void Serialize(in TBufferWriter bufferWriter, in T? value)
    public static byte[] Serialize(in T? value)
    public static ValueTask SerializeAsync(Stream stream, T? value)
    }
    var bufferWriter = ReusableLinkedArrayBufferWriterPool.Rent();
    var writer = new MemoryPackWriter(ref bufferWriter);
    writer.WriteValue(value);
    writer.Flush();
    await bufferWriter.WriteToAndResetAsync(stream);
    return bufferWriter.ToArrayAndReset();

    View Slide

  21. byte[] byte[] byte[]
    ArrayPool.Shared.Rent
    GetSpan()
    public sealed class ReusableLinkedArrayBufferWriter : IBufferWriter
    {
    List buffers;
    }
    struct BufferSegment
    {
    byte[] buffer;
    int written;
    }

    View Slide

  22. IBufferWriterを優先する

    View Slide

  23. Deserialize/MemoryPackReader

    View Slide

  24. View Slide

  25. public ref partial struct MemoryPackReader
    {
    ReadOnlySequence bufferSource;
    ref byte bufferReference;
    int bufferLength;
    ref byte GetSpanReference(int sizeHint);
    void Advance(int count);
    public MemoryPackReader(in ReadOnlySequence bufferSource)
    public MemoryPackReader(ReadOnlySpan buffer)
    }
    読み込み用バッファー管理

    View Slide

  26. 連結されたbyte[]、のようなもの

    View Slide

  27. 性能は同期バッファと非同期読み書きで決まる
    Streaming処理は別の機構で行う

    View Slide

  28. Optimize for All Types

    View Slide

  29. C#の配列は要素がunmanaged型(参照型を含まな
    いstruct)の場合、全て直列に並ぶ
    var srcLength = Unsafe.SizeOf() * value.Length;
    var allocSize = srcLength + 4;
    ref var dest = ref GetSpanReference(allocSize);
    ref var src = ref Unsafe.As(ref GetArrayDataReference(value));
    Unsafe.WriteUnaligned(ref dest, value.Length);
    Unsafe.CopyBlockUnaligned(ref Unsafe.Add(ref dest, 4), ref src, (uint)srcLength);
    Advance(allocSize);

    View Slide

  30. Vector3[]など複合型になればなるほど有利

    View Slide

  31. public sealed class ListFormatter : MemoryPackFormatter>
    {
    public override void Serialize(
    ref MemoryPackWriter writer, scoped ref List? value)
    {
    if (value == null)
    {
    writer.WriteNullCollectionHeader();
    return;
    }
    var span = CollectionsMarshal.AsSpan(value);
    var formatter = GetFormatter();
    WriteCollectionHeader(span.Length);
    for (int i = 0; i < span.Length; i++)
    {
    formatter.Serialize(ref this, ref span[i]);
    }
    }
    }

    View Slide

  32. public override void Deserialize(ref MemoryPackReader reader, scoped ref List? value)
    {
    if (!reader.TryReadCollectionHeader(out var length))
    {
    value = null;
    return;
    }
    value = new List(length);
    var span = CollectionsMarshalEx.CreateSpan(value, length);
    var formatter = GetFormatter();
    for (int i = 0; i < length; i++)
    {
    formatter.Deserialize(ref this, ref span[i]);
    }
    }
    internal static class CollectionsMarshalEx
    {
    public static Span CreateSpan(
    List list, int length)
    {
    list.EnsureCapacity(length);
    ref var view = ref Unsafe.As,
    ListView>(ref list);
    view._size = length;
    return view._items.AsSpan(0, length);
    }
    }
    internal sealed class ListView
    {
    public T[] _items;
    public int _size;
    public int _version;
    }

    View Slide

  33. Conclusion

    View Slide

  34. 究極のシリアライザーを作る
    MemoryPack
    https://github.com/Cysharp/MemoryPack/

    View Slide

  35. View Slide