Embed presentation
Download as PDF, PPTX






![MessagePack for C#(v2-preview)7public ref struct MessagePackWriterT Deserialize<T>(in ReadOnlySequence<byte> byteSequence)Span<byte> bytes = stackalloc byte[36];internal ref partial struct SequenceReader<T>where T : unmanaged, IEquatable<T>public ref byte GetPointer(int sizeHint)ref Entry v = ref entry[0];](/image.pl?url=https%3a%2f%2fimage.slidesharecdn.com%2f09261330roomayoshihumikawai-190926055210%2f75%2fUnite-Tokyo-2019-Understanding-C-Struct-All-Things-7-2048.jpg&f=jpg&w=240)
![MessagePack for C#(v2-preview)8public ref struct MessagePackWriterT Deserialize<T>(in ReadOnlySequence<byte> byteSequence)Span<byte> bytes = stackalloc byte[36];internal ref partial struct SequenceReader<T>where T : unmanaged, IEquatable<T>ref structSpan<T> = stackallocin parameterwhere : unmanagedpublic ref byte GetPointer(int sizeHint)ref Entry v = ref entry[0];ref returnref local](/image.pl?url=https%3a%2f%2fimage.slidesharecdn.com%2f09261330roomayoshihumikawai-190926055210%2f75%2fUnite-Tokyo-2019-Understanding-C-Struct-All-Things-8-2048.jpg&f=jpg&w=240)

























![Equalsの自動実装とBox化34— structはEqualsが実装されていない場合、自動的に以下のものが呼ばれる— めっちゃ遅い— Equalsは辞書のKeyにすると呼ばれる!そのため辞書のKeyにする構造体はIEquatable<T>とGetHashCodeのカスタム実装を必ず行うことinternal static bool DefaultEquals(object o1, object o2){RuntimeType o1_type = (RuntimeType)o1.GetType();RuntimeType o2_type = (RuntimeType)o2.GetType();object[] fields;InternalEquals(o1, o2, out fields);for (int i = 0; i < fields.Length; i += 2){object meVal = fields[i];object youVal = fields[i + 1];if (!meVal.Equals(youVal)) return false;}return true;}object, object比較のボクシングそもそもリフレクションで全フィールド比較(遅い)うえに、フィールドの戻り値もボクシング原理主義的には可能なもの全てのStructにカスタム実装を入れたほうがいい、ということになるけれど、あまりにも面倒なので、さすがにそこはピンポイント(辞書のKeyになるものだけ)でいいと思います](/image.pl?url=https%3a%2f%2fimage.slidesharecdn.com%2f09261330roomayoshihumikawai-190926055210%2f75%2fUnite-Tokyo-2019-Understanding-C-Struct-All-Things-34-2048.jpg&f=jpg&w=240)





![Mutable Struct is Evil but Useful40— 一時的な入れ物として使うものに向いてるpublic struct Enumerator : IEnumerator<T>{List<T> list;int index;int version;T current;}public struct BinaryReader{byte[] bytes;int offset;}List<T>は直接GetEnumeratorを呼べる状況ではstruct List<T>.Enumerator を返すためゼロアロケーションバイナリを読みすすめる際にReadXxxを呼ぶたびにoffsetを追加していくというステートを管理局所的にしか使わないのでclassじゃなくてもいい](/image.pl?url=https%3a%2f%2fimage.slidesharecdn.com%2f09261330roomayoshihumikawai-190926055210%2f75%2fUnite-Tokyo-2019-Understanding-C-Struct-All-Things-40-2048.jpg&f=jpg&w=240)
![ref struct41— スタックにしか置けないという制約がref struct– 元々はSpan<T>(System.Memory, .NET Standard 2.0外部ライブラリ)のため– Span<T>は連続したメモリ領域のビューで、配列のように扱える(NativeArrayみたいな)– 今までポインタでしか扱えなかったstackallocを自然に扱えて便利– しかしそれによってスタックにのみ確保したメモリ領域をヒープに移されると危険– フィールドに置けない(ref structのfieldの場合のみ可)、ボクシングできない、インターフェイスを実装できない、ジェネリクスの型引数にできない、などの制約がある— ビュー的なものや一時的にしか使わない状態を持つものには適用しやすい– 制約が多いので無理に使おうとするとハマりますが……Span<int> temp = stackalloc int[12];](/image.pl?url=https%3a%2f%2fimage.slidesharecdn.com%2f09261330roomayoshihumikawai-190926055210%2f75%2fUnite-Tokyo-2019-Understanding-C-Struct-All-Things-41-2048.jpg&f=jpg&w=240)
![internal ref struct TempList<T>{int index;T[] array;public ReadOnlySpan<T> Span => new ReadOnlySpan<T>(array, 0, index);public TempList(int initialCapacity){this.array = ArrayPool<T>.Shared.Rent(initialCapacity);this.index = 0;}public void Add(T value){if (array.Length <= index){var newArray = ArrayPool<T>.Shared.Rent(index * 2);Array.Copy(array, newArray, index);ArrayPool<T>.Shared.Return(array, true);array = newArray;}array[index++] = value;}public void Dispose(){ArrayPool<T>.Shared.Return(array, true); // clear for de-reference all.}}ArrayPool(System.Buffers, Unityでは似たようなものを自作すれば……)から確保済み配列を取得し使うDisposeで返却一時的にしか使わない配列を都度確保せずプールから取得するための構造(TempList<T>)プールを扱っているので、寿命は明確に短くあってほしいのでref struct](/image.pl?url=https%3a%2f%2fimage.slidesharecdn.com%2f09261330roomayoshihumikawai-190926055210%2f75%2fUnite-Tokyo-2019-Understanding-C-Struct-All-Things-42-2048.jpg&f=jpg&w=240)

![Avoid the copy, Everywhere44— 全て ref で引き回せばいい、とはいうものの現実的ではない— あまりにも最悪な書き心地になる!— あるいは全てunsafeでポインタで取り回すという手も……— Unity.Entitiesのソースコードはかなりそれに近い[StructLayout(LayoutKind.Sequential)]internal unsafe struct Archetype{public ArchetypeChunkData Chunks;public UnsafeChunkPtrList ChunksWithEmptySlots;public ChunkListMap FreeChunksBySharedComponents;public int EntityCount;public int ChunkCapacity;public int BytesPerInstance;public ComponentTypeInArchetype* Types;public int TypesCount;public int NonZeroSizedTypesCount;public int* Offsets;public int* SizeOfs;public int* BufferCapacities;public int* TypeMemoryOrder;public int* ManagedArrayOffset;public int NumManagedArrays;// ... まだまだいっぱいvoid AddArchetypeIfMatching(Archetype* archetype,EntityQueryData* query)(ECSより引用)とにかく巨大なStruct全部ポインタで引き回すからOK(?)(ECSはネイティブメモリを使ったり色々と固有の事情があるので一般論は適用できない)](/image.pl?url=https%3a%2f%2fimage.slidesharecdn.com%2f09261330roomayoshihumikawai-190926055210%2f75%2fUnite-Tokyo-2019-Understanding-C-Struct-All-Things-44-2048.jpg&f=jpg&w=240)







![Best practice to use `in`52— in はコンパイルすると([In][IsReadOnly]ref T t) になる— 読み取り専用のため、フィールドへの代入はできない— v3.x = 10.0f; // compile error— メソッド呼び出しは可能だが、中身が変わらない保証がないため防御的コピーが走る— そのため、うかつに多用すると防御的コピーにより、むしろ性能低下もありうる— もしそれがMutable Structだともはやわけわからないことに— (Vector3.magnitudeはプロパティなのでメソッド扱い)— 防御的コピーが走らない条件は– プロパティ/メソッドを呼ばないこと– あるいは readonly struct であること– readonly structは書き換わらないことが保証されるため防御的コピーが走らない](/image.pl?url=https%3a%2f%2fimage.slidesharecdn.com%2f09261330roomayoshihumikawai-190926055210%2f75%2fUnite-Tokyo-2019-Understanding-C-Struct-All-Things-52-2048.jpg&f=jpg&w=240)
![Best practice to use `in`53— in はコンパイルすると([In][IsReadOnly]ref T t) になる— 読み取り専用のため、フィールドへの代入はできない— v3.x = 10.0f; // compile error— メソッド呼び出しは可能だが、中身が変わらない保証がないため防御的コピーが走る— そのため、うかつに多用すると防御的コピーにより、むしろ性能低下もありうる— もしそれがMutable Structだともはやわけわからないことに— 防御的コピーが走らない条件は– プロパティ/メソッドを呼ばないこと– あるいは readonly struct であること– readonly structは書き換わらないことが保証されるため防御的コピーが走らないpublic readonly struct MyVector3{public readonly float x;public readonly float y;public readonly float z;public MyVector3(float x, float y, float z){this.x = x; this.y = y; this.z = z;}readonly structの条件は全てのフィールドがreadonlyであることref readonly structもOK原則inの引数はreadonly struct中でフィールドしか絶対触らないと力強く言えるならナシではないよくわからないなら基本使わない](/image.pl?url=https%3a%2f%2fimage.slidesharecdn.com%2f09261330roomayoshihumikawai-190926055210%2f75%2fUnite-Tokyo-2019-Understanding-C-Struct-All-Things-53-2048.jpg&f=jpg&w=240)


![改めてStructとは56— メモリを単純にマッピングした構造– そのことだけ意識すれば、あとはやりたい放題できる[StructLayout(LayoutKind.Sequential, Size = 16)]public struct Empty16{}public struct LongLong{public long X;public long Y;}どちらも連続して16バイトの領域を確保しているだけ、という意味(に読める)インデックス0とインデックス8にアクセスしやすくしているだけ(という風に読める)](/image.pl?url=https%3a%2f%2fimage.slidesharecdn.com%2f09261330roomayoshihumikawai-190926055210%2f75%2fUnite-Tokyo-2019-Understanding-C-Struct-All-Things-56-2048.jpg&f=jpg&w=240)
![57// 先頭6バイトがTimestamp, 後ろ10バイトがランダムというUlidという仕様(ソート可能なGuidの代替)[StructLayout(LayoutKind.Explicit, Size = 16)]public struct Ulid{// TimestampとRandomnessのはじまりの部分だけ持っておく(最悪なくても別にいい)[FieldOffset(0)] byte timestamp0;[FieldOffset(6)] byte randomness0;public static Ulid NewUlid(){var memory = default(Ulid); // 16バイト確保var timestampMilliseconds = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();ref var fisrtByte = ref Unsafe.As<long, byte>(ref timestampMilliseconds);Unsafe.Add(ref memory.timestamp0, 0) = Unsafe.Add(ref fisrtByte, 5);Unsafe.Add(ref memory.timestamp0, 1) = Unsafe.Add(ref fisrtByte, 4);Unsafe.Add(ref memory.timestamp0, 2) = Unsafe.Add(ref fisrtByte, 3);Unsafe.Add(ref memory.timestamp0, 3) = Unsafe.Add(ref fisrtByte, 2);Unsafe.Add(ref memory.timestamp0, 4) = Unsafe.Add(ref fisrtByte, 1);Unsafe.Add(ref memory.timestamp0, 5) = Unsafe.Add(ref fisrtByte, 0);Unsafe.WriteUnaligned(ref memory.randomness0, xorshift.NextULong());Unsafe.WriteUnaligned(ref Unsafe.Add(ref memory.randomness0, 2), xorshift.NextULong());return memory;}Ulid:[Timestamp(6),Randomness(10)]の実装(System.Runtime.CompilerServices.Unsafeを利用、Unityでも動きますが別途参照は必要、ポインタでやってもいいです)](/image.pl?url=https%3a%2f%2fimage.slidesharecdn.com%2f09261330roomayoshihumikawai-190926055210%2f75%2fUnite-Tokyo-2019-Understanding-C-Struct-All-Things-57-2048.jpg&f=jpg&w=240)
![58// 先頭6バイトがTimestamp, 後ろ10バイトがランダムというUlidという仕様(ソート可能なGuidの代替)[StructLayout(LayoutKind.Explicit, Size = 16)]public struct Ulid{// TimestampとRandomnessのはじまりの部分だけ持っておく(最悪なくても別にいい)[FieldOffset(0)] byte timestamp0;[FieldOffset(6)] byte randomness0;public static Ulid NewUlid(){var memory = default(Ulid); // 16バイト確保var timestampMilliseconds = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();ref var fisrtByte = ref Unsafe.As<long, byte>(ref timestampMilliseconds);Unsafe.Add(ref memory.timestamp0, 0) = Unsafe.Add(ref fisrtByte, 5);Unsafe.Add(ref memory.timestamp0, 1) = Unsafe.Add(ref fisrtByte, 4);Unsafe.Add(ref memory.timestamp0, 2) = Unsafe.Add(ref fisrtByte, 3);Unsafe.Add(ref memory.timestamp0, 3) = Unsafe.Add(ref fisrtByte, 2);Unsafe.Add(ref memory.timestamp0, 4) = Unsafe.Add(ref fisrtByte, 1);Unsafe.Add(ref memory.timestamp0, 5) = Unsafe.Add(ref fisrtByte, 0);Unsafe.WriteUnaligned(ref memory.randomness0, xorshift.NextULong());Unsafe.WriteUnaligned(ref Unsafe.Add(ref memory.randomness0, 2), xorshift.NextULong());return memory;}// メモリ領域をコピーすればおk。// 文字列表現としてBase32エンコード(ToString)も同様に自分のメモリ粋から算出public bool TryWriteBytes(Span<byte> destination){if (destination.Length < 16){return false;}Unsafe.WriteUnaligned(ref MemoryMarshal.GetReference(destination), this)return true;}出力先としてbyte[]とstringがあればそれでいいそれらはほとんどメモリコピーで実現する](/image.pl?url=https%3a%2f%2fimage.slidesharecdn.com%2f09261330roomayoshihumikawai-190926055210%2f75%2fUnite-Tokyo-2019-Understanding-C-Struct-All-Things-58-2048.jpg&f=jpg&w=240)
![Union59[StructLayout(LayoutKind.Explicit, Pack = 1)]internal struct GuidBits{[FieldOffset(0)]public readonly Guid Value;[FieldOffset(0)]public readonly byte Byte0;[FieldOffset(1)]public readonly byte Byte1;[FieldOffset(2)]public readonly byte Byte2;[FieldOffset(3)]public readonly byte Byte3;/* 中略(Byte4~Byte11) */[FieldOffset(12)]public readonly byte Byte12;[FieldOffset(13)]public readonly byte Byte13;[FieldOffset(14)]public readonly byte Byte14;[FieldOffset(15)]public readonly byte Byte15;Guidとbyte0~16の重ね合わせ(同一FieldOffset)通常Stringかbyte[]からしか生成できないGuidを、byte0~16を埋めるだけで自由に生成する(本来弄れないGuidとしてのメモリ領域を重ね合わせて安全に(not unsafe)弄る)MessagePack for C#でUtf8 Bytesのスライスから文字列のアロケーションを避けて直接Guidに変換するのに利用](/image.pl?url=https%3a%2f%2fimage.slidesharecdn.com%2f09261330roomayoshihumikawai-190926055210%2f75%2fUnite-Tokyo-2019-Understanding-C-Struct-All-Things-59-2048.jpg&f=jpg&w=240)
![改めてStructが要素の配列とは60— メモリにStructが単純に並んでいるX Y Z X Y Z X Y Z X Y ZVector3[]メモリをまるごとコピーするだけで最速のシリアライズだよね説(エンディアンは揃える)実際MagicOnionで有効にすることが可能(サーバーもC#なので直接メモリをぶん投げて受け取るのが簡単)ただしStructの中には参照型(String含む)は含めないこと。ポイン タをコピーしても意味がない](/image.pl?url=https%3a%2f%2fimage.slidesharecdn.com%2f09261330roomayoshihumikawai-190926055210%2f75%2fUnite-Tokyo-2019-Understanding-C-Struct-All-Things-60-2048.jpg&f=jpg&w=240)
![61public class UnsafeDirectBlitArrayFormatter<T> : IMessagePackFormatter<T[]> where T : struct{public unsafe int Serialize(ref byte[] bytes, int offset, T[] value){var startOffset = offset;var byteLen = value.Length * UnsafeUtility.SizeOf<T>();/* 中略(MsgPackでのExtヘッダー書き込み) */ulong handle2;var srcPointer = UnsafeUtility.PinGCArrayAndGetDataAddress(value, out handle2);try{fixed (void* dstPointer = &bytes[offset]){UnsafeUtility.MemCpy(dstPointer, srcPointer, byteLen);}}finally{UnsafeUtility.ReleaseGCObject(handle2);}// ...}T[]をbytesにシリアライズmemcpyするだけ](/image.pl?url=https%3a%2f%2fimage.slidesharecdn.com%2f09261330roomayoshihumikawai-190926055210%2f75%2fUnite-Tokyo-2019-Understanding-C-Struct-All-Things-61-2048.jpg&f=jpg&w=240)


![フレームワーク対応がないと意味がない64— Span<T>— 今までのAPIがT[]しか受け入れなかったりすると、結局T[]への変換が必要になる— 無駄アロケート— .NET Core 2.1で対応充実させ中— 例えばConvert.ToBase64Stringがbyte[]のほかReadOnlySpan<byte>を受けとる— つまりUnityではAPI側の対応がほとんどないのでSpanだけ入れても意味は限定的— NativeArray— DOTS周辺で使えるけれど、やはり一部のAPIは対応が必要— 例えばMesh.SetVertexBufferDataとしてList<T>やT[]以外にNativeArray<T>を受け取るようになったのは 2019.3から— というように、スムーズに全体的に統合されていくのはもうちょっとかな?](/image.pl?url=https%3a%2f%2fimage.slidesharecdn.com%2f09261330roomayoshihumikawai-190926055210%2f75%2fUnite-Tokyo-2019-Understanding-C-Struct-All-Things-64-2048.jpg&f=jpg&w=240)




2019/9/25-6に開催されたUnite Tokyo 2019の講演スライドです。河合 宜文(株式会社Cysharp)こんな人におすすめ・C#を極めたいエンジニア・パフォーマンスに興味のあるエンジニア・プログラミング言語マニア受講者が得られる知見・structに関する深い知識・パフォーマンス向上のヒント・C#の新しい文法と活用法Unityのイベント資料はこちらから:https://www.slideshare.net/UnityTechnologiesJapan/clipboards






![MessagePack for C#(v2-preview)7public ref struct MessagePackWriterT Deserialize<T>(in ReadOnlySequence<byte> byteSequence)Span<byte> bytes = stackalloc byte[36];internal ref partial struct SequenceReader<T>where T : unmanaged, IEquatable<T>public ref byte GetPointer(int sizeHint)ref Entry v = ref entry[0];](/image.pl?url=https%3a%2f%2fimage.slidesharecdn.com%2f09261330roomayoshihumikawai-190926055210%2f75%2fUnite-Tokyo-2019-Understanding-C-Struct-All-Things-7-2048.jpg&f=jpg&w=240)
![MessagePack for C#(v2-preview)8public ref struct MessagePackWriterT Deserialize<T>(in ReadOnlySequence<byte> byteSequence)Span<byte> bytes = stackalloc byte[36];internal ref partial struct SequenceReader<T>where T : unmanaged, IEquatable<T>ref structSpan<T> = stackallocin parameterwhere : unmanagedpublic ref byte GetPointer(int sizeHint)ref Entry v = ref entry[0];ref returnref local](/image.pl?url=https%3a%2f%2fimage.slidesharecdn.com%2f09261330roomayoshihumikawai-190926055210%2f75%2fUnite-Tokyo-2019-Understanding-C-Struct-All-Things-8-2048.jpg&f=jpg&w=240)

























![Equalsの自動実装とBox化34— structはEqualsが実装されていない場合、自動的に以下のものが呼ばれる— めっちゃ遅い— Equalsは辞書のKeyにすると呼ばれる!そのため辞書のKeyにする構造体はIEquatable<T>とGetHashCodeのカスタム実装を必ず行うことinternal static bool DefaultEquals(object o1, object o2){RuntimeType o1_type = (RuntimeType)o1.GetType();RuntimeType o2_type = (RuntimeType)o2.GetType();object[] fields;InternalEquals(o1, o2, out fields);for (int i = 0; i < fields.Length; i += 2){object meVal = fields[i];object youVal = fields[i + 1];if (!meVal.Equals(youVal)) return false;}return true;}object, object比較のボクシングそもそもリフレクションで全フィールド比較(遅い)うえに、フィールドの戻り値もボクシング原理主義的には可能なもの全てのStructにカスタム実装を入れたほうがいい、ということになるけれど、あまりにも面倒なので、さすがにそこはピンポイント(辞書のKeyになるものだけ)でいいと思います](/image.pl?url=https%3a%2f%2fimage.slidesharecdn.com%2f09261330roomayoshihumikawai-190926055210%2f75%2fUnite-Tokyo-2019-Understanding-C-Struct-All-Things-34-2048.jpg&f=jpg&w=240)





![Mutable Struct is Evil but Useful40— 一時的な入れ物として使うものに向いてるpublic struct Enumerator : IEnumerator<T>{List<T> list;int index;int version;T current;}public struct BinaryReader{byte[] bytes;int offset;}List<T>は直接GetEnumeratorを呼べる状況ではstruct List<T>.Enumerator を返すためゼロアロケーションバイナリを読みすすめる際にReadXxxを呼ぶたびにoffsetを追加していくというステートを管理局所的にしか使わないのでclassじゃなくてもいい](/image.pl?url=https%3a%2f%2fimage.slidesharecdn.com%2f09261330roomayoshihumikawai-190926055210%2f75%2fUnite-Tokyo-2019-Understanding-C-Struct-All-Things-40-2048.jpg&f=jpg&w=240)
![ref struct41— スタックにしか置けないという制約がref struct– 元々はSpan<T>(System.Memory, .NET Standard 2.0外部ライブラリ)のため– Span<T>は連続したメモリ領域のビューで、配列のように扱える(NativeArrayみたいな)– 今までポインタでしか扱えなかったstackallocを自然に扱えて便利– しかしそれによってスタックにのみ確保したメモリ領域をヒープに移されると危険– フィールドに置けない(ref structのfieldの場合のみ可)、ボクシングできない、インターフェイスを実装できない、ジェネリクスの型引数にできない、などの制約がある— ビュー的なものや一時的にしか使わない状態を持つものには適用しやすい– 制約が多いので無理に使おうとするとハマりますが……Span<int> temp = stackalloc int[12];](/image.pl?url=https%3a%2f%2fimage.slidesharecdn.com%2f09261330roomayoshihumikawai-190926055210%2f75%2fUnite-Tokyo-2019-Understanding-C-Struct-All-Things-41-2048.jpg&f=jpg&w=240)
![internal ref struct TempList<T>{int index;T[] array;public ReadOnlySpan<T> Span => new ReadOnlySpan<T>(array, 0, index);public TempList(int initialCapacity){this.array = ArrayPool<T>.Shared.Rent(initialCapacity);this.index = 0;}public void Add(T value){if (array.Length <= index){var newArray = ArrayPool<T>.Shared.Rent(index * 2);Array.Copy(array, newArray, index);ArrayPool<T>.Shared.Return(array, true);array = newArray;}array[index++] = value;}public void Dispose(){ArrayPool<T>.Shared.Return(array, true); // clear for de-reference all.}}ArrayPool(System.Buffers, Unityでは似たようなものを自作すれば……)から確保済み配列を取得し使うDisposeで返却一時的にしか使わない配列を都度確保せずプールから取得するための構造(TempList<T>)プールを扱っているので、寿命は明確に短くあってほしいのでref struct](/image.pl?url=https%3a%2f%2fimage.slidesharecdn.com%2f09261330roomayoshihumikawai-190926055210%2f75%2fUnite-Tokyo-2019-Understanding-C-Struct-All-Things-42-2048.jpg&f=jpg&w=240)

![Avoid the copy, Everywhere44— 全て ref で引き回せばいい、とはいうものの現実的ではない— あまりにも最悪な書き心地になる!— あるいは全てunsafeでポインタで取り回すという手も……— Unity.Entitiesのソースコードはかなりそれに近い[StructLayout(LayoutKind.Sequential)]internal unsafe struct Archetype{public ArchetypeChunkData Chunks;public UnsafeChunkPtrList ChunksWithEmptySlots;public ChunkListMap FreeChunksBySharedComponents;public int EntityCount;public int ChunkCapacity;public int BytesPerInstance;public ComponentTypeInArchetype* Types;public int TypesCount;public int NonZeroSizedTypesCount;public int* Offsets;public int* SizeOfs;public int* BufferCapacities;public int* TypeMemoryOrder;public int* ManagedArrayOffset;public int NumManagedArrays;// ... まだまだいっぱいvoid AddArchetypeIfMatching(Archetype* archetype,EntityQueryData* query)(ECSより引用)とにかく巨大なStruct全部ポインタで引き回すからOK(?)(ECSはネイティブメモリを使ったり色々と固有の事情があるので一般論は適用できない)](/image.pl?url=https%3a%2f%2fimage.slidesharecdn.com%2f09261330roomayoshihumikawai-190926055210%2f75%2fUnite-Tokyo-2019-Understanding-C-Struct-All-Things-44-2048.jpg&f=jpg&w=240)







![Best practice to use `in`52— in はコンパイルすると([In][IsReadOnly]ref T t) になる— 読み取り専用のため、フィールドへの代入はできない— v3.x = 10.0f; // compile error— メソッド呼び出しは可能だが、中身が変わらない保証がないため防御的コピーが走る— そのため、うかつに多用すると防御的コピーにより、むしろ性能低下もありうる— もしそれがMutable Structだともはやわけわからないことに— (Vector3.magnitudeはプロパティなのでメソッド扱い)— 防御的コピーが走らない条件は– プロパティ/メソッドを呼ばないこと– あるいは readonly struct であること– readonly structは書き換わらないことが保証されるため防御的コピーが走らない](/image.pl?url=https%3a%2f%2fimage.slidesharecdn.com%2f09261330roomayoshihumikawai-190926055210%2f75%2fUnite-Tokyo-2019-Understanding-C-Struct-All-Things-52-2048.jpg&f=jpg&w=240)
![Best practice to use `in`53— in はコンパイルすると([In][IsReadOnly]ref T t) になる— 読み取り専用のため、フィールドへの代入はできない— v3.x = 10.0f; // compile error— メソッド呼び出しは可能だが、中身が変わらない保証がないため防御的コピーが走る— そのため、うかつに多用すると防御的コピーにより、むしろ性能低下もありうる— もしそれがMutable Structだともはやわけわからないことに— 防御的コピーが走らない条件は– プロパティ/メソッドを呼ばないこと– あるいは readonly struct であること– readonly structは書き換わらないことが保証されるため防御的コピーが走らないpublic readonly struct MyVector3{public readonly float x;public readonly float y;public readonly float z;public MyVector3(float x, float y, float z){this.x = x; this.y = y; this.z = z;}readonly structの条件は全てのフィールドがreadonlyであることref readonly structもOK原則inの引数はreadonly struct中でフィールドしか絶対触らないと力強く言えるならナシではないよくわからないなら基本使わない](/image.pl?url=https%3a%2f%2fimage.slidesharecdn.com%2f09261330roomayoshihumikawai-190926055210%2f75%2fUnite-Tokyo-2019-Understanding-C-Struct-All-Things-53-2048.jpg&f=jpg&w=240)


![改めてStructとは56— メモリを単純にマッピングした構造– そのことだけ意識すれば、あとはやりたい放題できる[StructLayout(LayoutKind.Sequential, Size = 16)]public struct Empty16{}public struct LongLong{public long X;public long Y;}どちらも連続して16バイトの領域を確保しているだけ、という意味(に読める)インデックス0とインデックス8にアクセスしやすくしているだけ(という風に読める)](/image.pl?url=https%3a%2f%2fimage.slidesharecdn.com%2f09261330roomayoshihumikawai-190926055210%2f75%2fUnite-Tokyo-2019-Understanding-C-Struct-All-Things-56-2048.jpg&f=jpg&w=240)
![57// 先頭6バイトがTimestamp, 後ろ10バイトがランダムというUlidという仕様(ソート可能なGuidの代替)[StructLayout(LayoutKind.Explicit, Size = 16)]public struct Ulid{// TimestampとRandomnessのはじまりの部分だけ持っておく(最悪なくても別にいい)[FieldOffset(0)] byte timestamp0;[FieldOffset(6)] byte randomness0;public static Ulid NewUlid(){var memory = default(Ulid); // 16バイト確保var timestampMilliseconds = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();ref var fisrtByte = ref Unsafe.As<long, byte>(ref timestampMilliseconds);Unsafe.Add(ref memory.timestamp0, 0) = Unsafe.Add(ref fisrtByte, 5);Unsafe.Add(ref memory.timestamp0, 1) = Unsafe.Add(ref fisrtByte, 4);Unsafe.Add(ref memory.timestamp0, 2) = Unsafe.Add(ref fisrtByte, 3);Unsafe.Add(ref memory.timestamp0, 3) = Unsafe.Add(ref fisrtByte, 2);Unsafe.Add(ref memory.timestamp0, 4) = Unsafe.Add(ref fisrtByte, 1);Unsafe.Add(ref memory.timestamp0, 5) = Unsafe.Add(ref fisrtByte, 0);Unsafe.WriteUnaligned(ref memory.randomness0, xorshift.NextULong());Unsafe.WriteUnaligned(ref Unsafe.Add(ref memory.randomness0, 2), xorshift.NextULong());return memory;}Ulid:[Timestamp(6),Randomness(10)]の実装(System.Runtime.CompilerServices.Unsafeを利用、Unityでも動きますが別途参照は必要、ポインタでやってもいいです)](/image.pl?url=https%3a%2f%2fimage.slidesharecdn.com%2f09261330roomayoshihumikawai-190926055210%2f75%2fUnite-Tokyo-2019-Understanding-C-Struct-All-Things-57-2048.jpg&f=jpg&w=240)
![58// 先頭6バイトがTimestamp, 後ろ10バイトがランダムというUlidという仕様(ソート可能なGuidの代替)[StructLayout(LayoutKind.Explicit, Size = 16)]public struct Ulid{// TimestampとRandomnessのはじまりの部分だけ持っておく(最悪なくても別にいい)[FieldOffset(0)] byte timestamp0;[FieldOffset(6)] byte randomness0;public static Ulid NewUlid(){var memory = default(Ulid); // 16バイト確保var timestampMilliseconds = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();ref var fisrtByte = ref Unsafe.As<long, byte>(ref timestampMilliseconds);Unsafe.Add(ref memory.timestamp0, 0) = Unsafe.Add(ref fisrtByte, 5);Unsafe.Add(ref memory.timestamp0, 1) = Unsafe.Add(ref fisrtByte, 4);Unsafe.Add(ref memory.timestamp0, 2) = Unsafe.Add(ref fisrtByte, 3);Unsafe.Add(ref memory.timestamp0, 3) = Unsafe.Add(ref fisrtByte, 2);Unsafe.Add(ref memory.timestamp0, 4) = Unsafe.Add(ref fisrtByte, 1);Unsafe.Add(ref memory.timestamp0, 5) = Unsafe.Add(ref fisrtByte, 0);Unsafe.WriteUnaligned(ref memory.randomness0, xorshift.NextULong());Unsafe.WriteUnaligned(ref Unsafe.Add(ref memory.randomness0, 2), xorshift.NextULong());return memory;}// メモリ領域をコピーすればおk。// 文字列表現としてBase32エンコード(ToString)も同様に自分のメモリ粋から算出public bool TryWriteBytes(Span<byte> destination){if (destination.Length < 16){return false;}Unsafe.WriteUnaligned(ref MemoryMarshal.GetReference(destination), this)return true;}出力先としてbyte[]とstringがあればそれでいいそれらはほとんどメモリコピーで実現する](/image.pl?url=https%3a%2f%2fimage.slidesharecdn.com%2f09261330roomayoshihumikawai-190926055210%2f75%2fUnite-Tokyo-2019-Understanding-C-Struct-All-Things-58-2048.jpg&f=jpg&w=240)
![Union59[StructLayout(LayoutKind.Explicit, Pack = 1)]internal struct GuidBits{[FieldOffset(0)]public readonly Guid Value;[FieldOffset(0)]public readonly byte Byte0;[FieldOffset(1)]public readonly byte Byte1;[FieldOffset(2)]public readonly byte Byte2;[FieldOffset(3)]public readonly byte Byte3;/* 中略(Byte4~Byte11) */[FieldOffset(12)]public readonly byte Byte12;[FieldOffset(13)]public readonly byte Byte13;[FieldOffset(14)]public readonly byte Byte14;[FieldOffset(15)]public readonly byte Byte15;Guidとbyte0~16の重ね合わせ(同一FieldOffset)通常Stringかbyte[]からしか生成できないGuidを、byte0~16を埋めるだけで自由に生成する(本来弄れないGuidとしてのメモリ領域を重ね合わせて安全に(not unsafe)弄る)MessagePack for C#でUtf8 Bytesのスライスから文字列のアロケーションを避けて直接Guidに変換するのに利用](/image.pl?url=https%3a%2f%2fimage.slidesharecdn.com%2f09261330roomayoshihumikawai-190926055210%2f75%2fUnite-Tokyo-2019-Understanding-C-Struct-All-Things-59-2048.jpg&f=jpg&w=240)
![改めてStructが要素の配列とは60— メモリにStructが単純に並んでいるX Y Z X Y Z X Y Z X Y ZVector3[]メモリをまるごとコピーするだけで最速のシリアライズだよね説(エンディアンは揃える)実際MagicOnionで有効にすることが可能(サーバーもC#なので直接メモリをぶん投げて受け取るのが簡単)ただしStructの中には参照型(String含む)は含めないこと。ポイン タをコピーしても意味がない](/image.pl?url=https%3a%2f%2fimage.slidesharecdn.com%2f09261330roomayoshihumikawai-190926055210%2f75%2fUnite-Tokyo-2019-Understanding-C-Struct-All-Things-60-2048.jpg&f=jpg&w=240)
![61public class UnsafeDirectBlitArrayFormatter<T> : IMessagePackFormatter<T[]> where T : struct{public unsafe int Serialize(ref byte[] bytes, int offset, T[] value){var startOffset = offset;var byteLen = value.Length * UnsafeUtility.SizeOf<T>();/* 中略(MsgPackでのExtヘッダー書き込み) */ulong handle2;var srcPointer = UnsafeUtility.PinGCArrayAndGetDataAddress(value, out handle2);try{fixed (void* dstPointer = &bytes[offset]){UnsafeUtility.MemCpy(dstPointer, srcPointer, byteLen);}}finally{UnsafeUtility.ReleaseGCObject(handle2);}// ...}T[]をbytesにシリアライズmemcpyするだけ](/image.pl?url=https%3a%2f%2fimage.slidesharecdn.com%2f09261330roomayoshihumikawai-190926055210%2f75%2fUnite-Tokyo-2019-Understanding-C-Struct-All-Things-61-2048.jpg&f=jpg&w=240)


![フレームワーク対応がないと意味がない64— Span<T>— 今までのAPIがT[]しか受け入れなかったりすると、結局T[]への変換が必要になる— 無駄アロケート— .NET Core 2.1で対応充実させ中— 例えばConvert.ToBase64Stringがbyte[]のほかReadOnlySpan<byte>を受けとる— つまりUnityではAPI側の対応がほとんどないのでSpanだけ入れても意味は限定的— NativeArray— DOTS周辺で使えるけれど、やはり一部のAPIは対応が必要— 例えばMesh.SetVertexBufferDataとしてList<T>やT[]以外にNativeArray<T>を受け取るようになったのは 2019.3から— というように、スムーズに全体的に統合されていくのはもうちょっとかな?](/image.pl?url=https%3a%2f%2fimage.slidesharecdn.com%2f09261330roomayoshihumikawai-190926055210%2f75%2fUnite-Tokyo-2019-Understanding-C-Struct-All-Things-64-2048.jpg&f=jpg&w=240)


