【Unite Tokyo 2019】Understanding C# Struct All Things

Understanding
C# Struct
All Things
Cysharp, Inc.
Kawai Yoshifumi
About Speaker
2
— 河合 宜文 / Kawai Yoshifumi / @neuecc
— Cysharp, Inc. – CEO/CTO
— Microsoft MVP for Developer Technologies(C#)
— 50以上のOSS公開(UniRx, MagicOnion, MessagePack for C#, etc..)
— 株式会社Cysharp
— 2019年9月, Cygamesの子会社として設立
— C#関連の研究開発/OSS/コンサルティングを行う
— C#大統一理論(サーバー/クライアントともにC#で実装する)を推進
https://github.com/Cysharp
OSS for Unity – GitHub/Cysharp
3
UniTask ★288
Provides an efficient async/await integration to
Unity.
RuntimeUnitTestToolkit ★87
CLI/GUI Frontend of Unity Test Runner to test on
any platforms.
RandomFixtureKit ★16
Fill random/edge-case value to target type for
unit testing.
MagicOnion ★1240
Unified Realtime/API Engine for .NET Core and
Unity.
MasterMemory ★407
Embedded Typed Readonly In-Memory
Document Database for .NET Core and Unity.
https://github.com/neuecc
OSS for Unity – GitHub/neuecc
4
LINQ-to-GameObject-for-Unity ★448
Traverse GameObject Hierarchy by LINQ.
PhotonWire ★92
Typed Asynchronous RPC Layer for Photon.
SerializableDictionary ★87
SerializableCollections for Unity.
ReMotion ★27
Hyper Fast Reactive Tween Engine for Unity.
UniRx ★3722
Reactive Extensions for Unity.
MessagePack-CSharp ★2089
Extremely Fast MessagePack Serializer.
ZeroFormatter ★1778
Infinitely Fast Deserializer.
Utf8Json ★1352
Definitely Fastest JSON Serializer.
【Unite Tokyo 2019】Understanding C# Struct All Things
6
The Evolution of C# Struct
MessagePack for C#(v2-preview)
7
public ref struct MessagePackWriter
T 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];
MessagePack for C#(v2-preview)
8
public ref struct MessagePackWriter
T Deserialize<T>(in ReadOnlySequence<byte> byteSequence)
Span<byte> bytes = stackalloc byte[36];
internal ref partial struct SequenceReader<T>
where T : unmanaged, IEquatable<T>
ref struct
Span<T> = stackalloc
in parameter
where : unmanaged
public ref byte GetPointer(int sizeHint)
ref Entry v = ref entry[0];
ref return
ref local
DOTS(昨日の基調講演より)
DOTS(昨日の基調講演より)
What’s new struct features
11
C# 7.2 C# 7.3 C# 8.0
in modifier on parameter
ref readonly modifier on
method returns, local
readonly struct
declaration
ref struct declaration
ref extension method
stackalloc to Span
reassign ref local
additional generics
constraints(unmanage
d, Enum, Delegate)
stackalloc initializer
access fixed field
without pinning
readonly method
Disposable ref structs
Unmanaged
constructed types
https://docs.microsoft.com/en-us/dotnet/csharp/whats-new/
C# 7.0
ref return statement
ref local variables
12
Which Unity Version should we support?
Unity Version C# Version .NET Version
Unity 2017.4 6.0 .NET 3.5/.NET 4.6
Unity 2018.2 6.0 .NET 4.x/Standard 2.0
Unity 2018.3 7.3 .NET 4.x/Standard 2.0
Unity 2018.4 7.3 .NET 4.x/Standard 2.0
Unity 2019.1 7.3 .NET 4.x/Standard 2.0
13
Which Unity Version should we support?
Unity Version C# Version .NET Version
Unity 2017.4 6.0 .NET 3.5/.NET 4.6
Unity 2018.2 6.0 .NET 4.x/Standard 2.0
Unity 2018.3 7.3 .NET 4.x/Standard 2.0
Unity 2018.4 7.3 .NET 4.x/Standard 2.0
Unity 2019.1 7.3 .NET 4.x/Standard 2.0
2018.3以上一択、それ以下の
バージョンはNot Supported
14
Which Unity Version should we support?
Unity Version C# Version .NET Version
Unity 2017.4 6.0 .NET 3.5/.NET 4.6
Unity 2018.2 6.0 .NET 4.x/Standard 2.0
Unity 2018.3 7.3 .NET 4.x/Standard 2.0
Unity 2018.4 7.3 .NET 4.x/Standard 2.0
Unity 2019.1 7.3 .NET 4.x/Standard 2.0
2018.3から以下の言語に関するdefineが使える
CSHARP_7_3_OR_NEWER
以下のどちらか選んだほうが定義される
NET_4_6
NET_STANDARD_2_0
Struct is important for Performance!
15
— C# 7以降の急速なstruct強化はパフォーマンスのため
— それは .NET Core でも、Unityでも
— アプローチは異なれど、両者とも構造体をパフォーマンスのため活用している
— 特にUnityの推すDOTS(Data Oriented Technology Stack)はstructの塊
— 今、全てを学び、備えよう
public unsafe ref struct BlobBuilderArray<T>
where T : struct
public unsafe static ref T AsRef<T>(void* ptr) where T : struct
public readonly struct BuildComponentDataToEntityLookupTask<TComponentData> : IDisposable
where TComponentData : unmanaged, IComponentData, IEquatable<TComponentData>
Unity ECSの中の
C# 7.3表現
16
The Basic of C# Memory
The Memory of C#
17
AppDomain(Managed)
Thread
Stack
HeapThread
Stack
Unmanaged
The Memory of C#
18
AppDomain(Managed)
Thread
Stack
HeapThread
Stack
Unmanaged
ローカル変数はスタック領域に格納される
ヒープ領域に確保されたデータは
GCの管理化に入る
UnityではC#管理外のメモリを扱う
こともよくある(特にDOTS)
Let’s see memory layout
19
— SharpLabでメモリの中身を見よう!
— https://sharplab.io/
— C# to IL, C# to C#, C# to ASMなど豊富な機能がある
— Run と Inspectを組み合わせるとメモリの中身が見れる
なお、組み込み型の場合、Unity(mono)
とSharpLab(.NET Core)で中身が異なる
ことがある場合に注意
Struct Memory
Int(4バイト)のX, Y, Z(12バイト)が
素直にメモリ上(スタック)に並ぶ
Class Memory
スタック上の変数はヒープ上
のアドレスを指す
ヒープ上に確保されるメモリは管理
用のヘッダ/型情報 + 実データ
Pass by Reference/Pass by Value
22
— C#はデフォルトは全て「値渡し」
— つまりコピーされます
— ローカル変数への代入もコピー
(T x) (ref T x)
class 参照の値渡し 参照の参照渡し
struct 値の値渡し 値の参照渡し
スタック領域は大体この辺っぽい
00 00 00 00 からそれっぽ
いデータが入った気配
引き続き、 y = x で同じデ
ータが追加で入ったっぽい
別のところにxとyがコピー
されて入った気配
戻りのzを受け取って全部
埋まった
int x
int y
int z
コンパイル時(C# -> IL)の段
階で変数の置き場確保してお
きます、的な
Structの基本的な原則
29
— 全てはコピーに気をつける、ということ
— クラスとの違い、ほとんどの問題はコピーにより引き起こされる
— 大きなサイズの構造体を(基本的には)作らない
– IntPtr.Size(4 or 8バイト)以上は参照渡しに比べて大きいということになる
– それだと制限キツすぎなので、一般には16バイト以下ぐらいを目安に
— 変更可能な構造体を(基本的には)作らない
– コピーされることによって、変更したつもりが変更されない
– 一度は悩むVector3変更されない問題
Structの基本的な原則
30
— 全てはコピーに気をつける、ということ
— クラスとの違い、ほとんどの問題はコピーにより引き起こされる
— 大きなサイズの構造体を(基本的には)作らない
– IntPtr.Size(4 or 8バイト)以上は参照渡しに比べて大きいということになる
– それだと制限キツすぎなので、一般には16バイト以下ぐらいを目安に
— 変更可能な構造体を(基本的には)作らない
– コピーされることによって、変更したつもりが変更されない
– 一度は悩むVector3変更されない問題
// position変えたつもりが変わらない!
this.transform.position.Set(10f, 20f, 30f);
// つまりこういうことだから
this.transform.INTERNAL_get_position(out Vector3 value);
value.Set(10f, 20f, 30f);
Structの基本的な原則
31
— 全てはコピーに気をつける、ということ
— クラスとの違い、ほとんどの問題はコピーにより引き起こされる
— 大きなサイズの構造体を(基本的には)作らない
– IntPtr.Size(4 or 8バイト)以上は参照渡しに比べて大きいということになる
– それだと制限キツすぎなので、一般には16バイト以下ぐらいを目安に
— 変更可能な構造体を(基本的には)作らない
– コピーされることによって、変更したつもりが変更されない
– 一度は悩むVector3変更されない問題
// position変えたつもりが変わらない!
this.transform.position.Set(10f, 20f, 30f);
// つまりこういうことだから
this.transform.INTERNAL_get_position(out Vector3 value);
value.Set(10f, 20f, 30f);
positionがプロパティなのが悪い
(フィールドならコピーの問題は起きない)
が、これはtransformのデータの実体は
アンマネージドメモリ側(UnityEngine)にある
ためという、Unityならではの悩みでもある
(C#/C++越境がどうしても抱える話)
Boxed Struct
ボックス化
スタック上の変数は
ヒープのアドレス ヒープ上にクラスと
同様ヘッダが付いた
うえで領域確保
アンボックスの毎に
ここからスタックへ
コピーする
Box化との戦い
33
— ボックス化はnewと一緒
— むしろアンボックスの頻度的により悪い
— ボックス化が避けられないなら最初からクラスで作ることも選択肢
— インターフェイスへのキャストに気をつける
– ジェネリクスを使って回避していく
– enumの場合はEnum(参照型)も同様
public static class BoxedInt
{
public static readonly object Zero = 0;
public static readonly object One = 1;
public static readonly object MinusOne = -1;
}
ある程度決まった値が頻繁にボッ
クス化されるようなら、先に作っ
ておいて使い回すという技
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になるもの
だけ)でいいと思います
Struct Layout and Padding
単純計算ではbyte(1) + long(8) + int(4) = 13ですが、
アラインメント調整のため、最長の8にそれぞれが合
わせられて8 * 3 = 24バイトの確保になっている
Struct Layout and Padding StructLayoutやFieldOffsetによってレイアウ
トはカスタマイズ可能。
structのデフォルトはSequential(宣言順)
Autoに変えると、ZとXが詰められることで最
小の16バイトに縮む
参照型はstructと異なりデフォ
ルトがAuto
Heap Layout: Array
オブジェクトの共通ヘッダの後
ろに長さが付いてる
データ領域には要素がそのまま順番に並ぶ。構造体な
ら、値がそのまま順番に並んでいることになる(参照
型の場合はポインタが並んでいるため、実態のデータ
を更に辿る必要がある)
38
ref and readonly
Zero Allocation foreach in List<T>
39
— foreachは .GetEnumerator -> while(MoveNext()) に変換される
— (ただし配列の場合はコンパイル時にILでforに変換される)
— つまり IEnumerator が生成されてヒープに確保されている?
— されるようでされない
var list = new List<int>() { 1, 2, 3 };
foreach (var item in list)
{
/* do anything */
}
Mutable Struct is Evil but Useful
40
— 一時的な入れ物として使うものに向いてる
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じゃなくてもいい
ref struct
41
— スタックにしか置けないという制約がref struct
– 元々はSpan<T>(System.Memory, .NET Standard 2.0外部ライブラリ)のため
– Span<T>は連続したメモリ領域のビューで、配列のように扱える(NativeArrayみたいな)
– 今までポインタでしか扱えなかったstackallocを自然に扱えて便利
– しかしそれによってスタックにのみ確保したメモリ領域をヒープに移されると危険
– フィールドに置けない(ref structのfieldの場合のみ可)、ボクシングできない、インターフ
ェイスを実装できない、ジェネリクスの型引数にできない、などの制約がある
— ビュー的なものや一時的にしか使わない状態を持つものには適用しやすい
– 制約が多いので無理に使おうとするとハマりますが……
Span<int> temp = stackalloc int[12];
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
public void DoNanika(IEnumerable<int> idList)
{
var resources = idList.Select(x => Load(x));
// LINQの遅延実行により二回のLoadが走ってしまう
// それを避けるために .ToList() するとそれはそれでListの無駄を感じる
foreach (var item in resources) { /* nanika suru 1 */ }
foreach (var item in resources) { /* nanika suru 2 */ }
}
public void DoNanika(IEnumerable<int> idList)
{
using var resources = idList.Select(x => Load(x)).ToTempList();
foreach (var item in resources) { /* nanika suru 1 */ }
foreach (var item in resources) { /* nanika suru 2 */ }
}
usingだけで末尾で
Disposeが便利
(C# 8.0から!
Unityではまだ!)
ここの中だけで使う一時配列はPoolから
取ってるのでアロケートなしで済んだ
Avoid the copy, Everywhere
44
— 全て 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はネイティブメモリを使ったり色々
と固有の事情があるので一般論は適用で
きない)
static void Normal(Vector3 v3)
{
}
static void In(in Vector3 v3)
{
}
static void Ref(ref Vector3 v3)
{
}
Avoid the copy, Everywhere
45
そこで登場するのが新しいキーワード
“in”
(このコード例自体には何の意味もないです)
static void Normal(Vector3 v3)
{
}
static void In(in Vector3 v3)
{
}
static void Ref(ref Vector3 v3)
{
}
Avoid the copy, Everywhere
46
Normal(v3);
In(v3);
Ref(ref v3);
呼び側のコード
static void Normal(Vector3 v3)
{
}
static void In(in Vector3 v3)
{
}
static void Ref(ref Vector3 v3)
{
}
Avoid the copy, Everywhere
47
ldloc.0
call Noraml
ldloca.0
call In
ldloca.0
call Ref
Normal(v3);
In(v3);
Ref(ref v3);
呼び側のIL
static void Normal(Vector3 v3)
{
}
static void In(in Vector3 v3)
{
}
static void Ref(ref Vector3 v3)
{
}
Avoid the copy, Everywhere
48
ldloc.0
call Noraml
ldloca.0
call In
ldloca.0
call Ref
呼び方は普通と一緒なのに
refと同じく参照渡しされる!
Normal(v3);
In(v3);
Ref(ref v3);
じゃあ全部 in でいいね!
(にはならない)
static void Normal(Vector3 v3)
{
_ = v3.magnitude;
}
static void In(in Vector3 v3)
{
_ = v3.magnitude;
}
static void Ref(ref Vector3 v3)
{
_ = v3.magnitude;
}
Avoid the copy, Everywhere
49
ldarga.0
call get_magnitude
ldarg.0
ldobj
stloc.0
ldloca.0
call get_magnitude
ldarg.0
call get_magnitude
呼ばれ側のIL
static void Normal(Vector3 v3)
{
_ = v3.magnitude;
}
static void In(in Vector3 v3)
{
_ = v3.magnitude;
}
static void Ref(ref Vector3 v3)
{
_ = v3.magnitude;
}
Avoid the copy, Everywhere
50
ldarga.0
call get_magnitude
ldarg.0
ldobj
stloc.0
ldloca.0
call get_magnitude
ldarg.0
call get_magnitude
呼ばれ側のIL
コピーされてる(防御的コピー)
var v3_2 = v3;
_ = v3_2.magnitude;
static void Normal(Vector3 v3)
{
_ = v3.magnitude;
_ = v3.magnitude;
}
static void In(in Vector3 v3)
{
_ = v3.magnitude;
_ = v3.magnitude;
}
static void Ref(ref Vector3 v3)
{
_ = v3.magnitude;
_ = v3.magnitude;
}
Avoid the copy, Everywhere
51
ldarga.0
call get_magnitude
ldarg.0
ldobj
stloc.0
ldloca.0
call get_magnitude
ldarg.0
call get_magnitude
呼ばれ側のIL
二回呼べばコピーもそのまま二つ
ldarga.0
call get_magnitude
ldarg.0
ldobj
stloc.0
ldloca.0
call get_magnitude
ldarg.0
call get_magnitude
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は書き換わらないことが保証されるため防御的コピーが走らない
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
中でフィールドしか絶対触らないと力
強く言えるならナシではない
よくわからないなら基本使わない
readonly field(struct)の罠
54
— readonlyなfieldのstructにmutableな操作を行っても変更されない
— よって、ミュータブルな操作を行うものはreadonly fieldにすべきではない
— 原則的には「可能なものは」と言いたいところだけど
Unityの場合、可能なものが多いので……
public class NantokaBehaviour : MonoBehaviour
{
public readonly Vector3 NanikanoVector3;
public void Incr()
{
NanikanoVector3.Set(
NanikanoVector3.x + 1f,
NanikanoVector3.y + 1f,
NanikanoVector3.z + 1f);
}
}
何回Incr呼んでも0, 0, 0のまま
55
Manipulate Memory
改めてStructとは
56
— メモリを単純にマッピングした構造
– そのことだけ意識すれば、あとはやりたい放題できる
[StructLayout(LayoutKind.Sequential, Size = 16)]
public struct Empty16
{
}
public struct LongLong
{
public long X;
public long Y;
}
どちらも連続して16バイトの
領域を確保しているだけ、とい
う意味(に読める)
インデックス0とインデックス
8にアクセスしやすくしている
だけ(という風に読める)
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
.CompilerService
s.Unsafeを利用、
Unityでも動きま
すが別途参照は必
要、ポインタでや
ってもいいです)
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があればそれでいい
それらはほとんどメモリコピーで実現する
Union
59
[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に変換するのに利用
改めてStructが要素の配列とは
60
— メモリにStructが単純に並んでいる
X Y Z X Y Z X Y Z X Y ZVector3[]
メモリをまるごとコピーするだけで
最速のシリアライズだよね説
(エンディアンは揃える)
実際MagicOnionで有効にすることが可
能(サーバーもC#なので直接メモリをぶ
ん投げて受け取るのが簡単)
ただしStructの中には参照型(String含
む)は含めないこと。ポイン タをコピー
しても意味がない
61
public 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するだけ
62
Span and NativeArray
Span vs NativeArray
63
— Span<T>
— System.Memory
— .NET Standard 2.1では標準(現在は外部ライブラリが必要)
— C# 7.2と統合されている
— あらゆる連続したメモリ領域のビュー
— 配列、stackalloc、ネイティブメモリ(ポインタ)、文字列(String)
— NativeArray
— Unity固有, 特にDOTSのキーパーツ
— UnsafeUtility.Malloc で獲得するUnmanaged Memoryのビュー
フレームワーク対応がないと意味がない
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から
— というように、スムーズに全体的に統合されていくのはもうちょっとかな?
65
Conclusion
の前に。
66
— 構造体の色をクラスとは別の色に変更しておこう!
– 性能特性が異なるもののため、見分けがつくと、とても楽になる
Structを恐れない
67
— Structの使いこなし自体は、もはや必須
– Classを優先する牧歌的時代は終わった
– 確かに罠は(いっぱい)あるが、難しい話ではない
– 覚えるパターンが(ちょっと)多いだけで
— そしてこの流れは戻らない
– 言語強化から、フレームワークの抜本的変更まで、時代は既に来てる
– ……とはいえ、じゃあいきなりめっちゃ使うかと言うと、それはまた別の話
– 使うべき時に使い、読めるようにするという、当たり前を大事にしよう
1 of 67

Recommended

今日からできる!簡単 .NET 高速化 Tips by
今日からできる!簡単 .NET 高速化 Tips今日からできる!簡単 .NET 高速化 Tips
今日からできる!簡単 .NET 高速化 TipsTakaaki Suzuki
35.1K views53 slides
CEDEC 2018 最速のC#の書き方 - C#大統一理論へ向けて性能的課題を払拭する by
CEDEC 2018 最速のC#の書き方 - C#大統一理論へ向けて性能的課題を払拭するCEDEC 2018 最速のC#の書き方 - C#大統一理論へ向けて性能的課題を払拭する
CEDEC 2018 最速のC#の書き方 - C#大統一理論へ向けて性能的課題を払拭するYoshifumi Kawai
74.7K views94 slides
ネットワーク ゲームにおけるTCPとUDPの使い分け by
ネットワーク ゲームにおけるTCPとUDPの使い分けネットワーク ゲームにおけるTCPとUDPの使い分け
ネットワーク ゲームにおけるTCPとUDPの使い分けモノビット エンジン
61.4K views63 slides
async/await のしくみ by
async/await のしくみasync/await のしくみ
async/await のしくみ信之 岩永
19.8K views41 slides
目grep入門 +解説 by
目grep入門 +解説目grep入門 +解説
目grep入門 +解説murachue
89.4K views83 slides
こわくない Git by
こわくない Gitこわくない Git
こわくない GitKota Saito
881.5K views186 slides

More Related Content

What's hot

.NET Core 3.0時代のメモリ管理 by
.NET Core 3.0時代のメモリ管理.NET Core 3.0時代のメモリ管理
.NET Core 3.0時代のメモリ管理KageShiron
2K views40 slides
【Unite Tokyo 2018】さては非同期だなオメー!async/await完全に理解しよう by
【Unite Tokyo 2018】さては非同期だなオメー!async/await完全に理解しよう【Unite Tokyo 2018】さては非同期だなオメー!async/await完全に理解しよう
【Unite Tokyo 2018】さては非同期だなオメー!async/await完全に理解しようUnity Technologies Japan K.K.
47.9K views68 slides
オンラインゲームの仕組みと工夫 by
オンラインゲームの仕組みと工夫オンラインゲームの仕組みと工夫
オンラインゲームの仕組みと工夫Yuta Imai
869.9K views56 slides
Dockerからcontainerdへの移行 by
Dockerからcontainerdへの移行Dockerからcontainerdへの移行
Dockerからcontainerdへの移行Kohei Tokunaga
16.7K views36 slides
【Unite Tokyo 2019】運用中超大規模タイトルにおけるUnityアップデート課題の解決手法と事例 by
【Unite Tokyo 2019】運用中超大規模タイトルにおけるUnityアップデート課題の解決手法と事例【Unite Tokyo 2019】運用中超大規模タイトルにおけるUnityアップデート課題の解決手法と事例
【Unite Tokyo 2019】運用中超大規模タイトルにおけるUnityアップデート課題の解決手法と事例UnityTechnologiesJapan002
7.3K views105 slides
MagicOnion入門 by
MagicOnion入門MagicOnion入門
MagicOnion入門torisoup
10.4K views37 slides

What's hot(20)

.NET Core 3.0時代のメモリ管理 by KageShiron
.NET Core 3.0時代のメモリ管理.NET Core 3.0時代のメモリ管理
.NET Core 3.0時代のメモリ管理
KageShiron2K views
【Unite Tokyo 2018】さては非同期だなオメー!async/await完全に理解しよう by Unity Technologies Japan K.K.
【Unite Tokyo 2018】さては非同期だなオメー!async/await完全に理解しよう【Unite Tokyo 2018】さては非同期だなオメー!async/await完全に理解しよう
【Unite Tokyo 2018】さては非同期だなオメー!async/await完全に理解しよう
オンラインゲームの仕組みと工夫 by Yuta Imai
オンラインゲームの仕組みと工夫オンラインゲームの仕組みと工夫
オンラインゲームの仕組みと工夫
Yuta Imai869.9K views
Dockerからcontainerdへの移行 by Kohei Tokunaga
Dockerからcontainerdへの移行Dockerからcontainerdへの移行
Dockerからcontainerdへの移行
Kohei Tokunaga16.7K views
【Unite Tokyo 2019】運用中超大規模タイトルにおけるUnityアップデート課題の解決手法と事例 by UnityTechnologiesJapan002
【Unite Tokyo 2019】運用中超大規模タイトルにおけるUnityアップデート課題の解決手法と事例【Unite Tokyo 2019】運用中超大規模タイトルにおけるUnityアップデート課題の解決手法と事例
【Unite Tokyo 2019】運用中超大規模タイトルにおけるUnityアップデート課題の解決手法と事例
MagicOnion入門 by torisoup
MagicOnion入門MagicOnion入門
MagicOnion入門
torisoup10.4K views
20分くらいでわかった気分になれるC++20コルーチン by yohhoy
20分くらいでわかった気分になれるC++20コルーチン20分くらいでわかった気分になれるC++20コルーチン
20分くらいでわかった気分になれるC++20コルーチン
yohhoy13K views
【Unite Tokyo 2019】Unity Test Runnerを活用して内部品質を向上しよう by UnityTechnologiesJapan002
【Unite Tokyo 2019】Unity Test Runnerを活用して内部品質を向上しよう【Unite Tokyo 2019】Unity Test Runnerを活用して内部品質を向上しよう
【Unite Tokyo 2019】Unity Test Runnerを活用して内部品質を向上しよう
Unity 2018-2019を見据えたDeNAのUnity開発のこれから [DeNA TechCon 2019] by DeNA
Unity 2018-2019を見据えたDeNAのUnity開発のこれから [DeNA TechCon 2019]Unity 2018-2019を見据えたDeNAのUnity開発のこれから [DeNA TechCon 2019]
Unity 2018-2019を見据えたDeNAのUnity開発のこれから [DeNA TechCon 2019]
DeNA23.1K views
すごい constexpr たのしくレイトレ! by Genya Murakami
すごい constexpr たのしくレイトレ!すごい constexpr たのしくレイトレ!
すごい constexpr たのしくレイトレ!
Genya Murakami25.6K views
中3女子でもわかる constexpr by Genya Murakami
中3女子でもわかる constexpr中3女子でもわかる constexpr
中3女子でもわかる constexpr
Genya Murakami49K views
【CEDEC2017】Unityを使ったNintendo Switch™向けのタイトル開発・移植テクニック!! by Unity Technologies Japan K.K.
【CEDEC2017】Unityを使ったNintendo Switch™向けのタイトル開発・移植テクニック!!【CEDEC2017】Unityを使ったNintendo Switch™向けのタイトル開発・移植テクニック!!
【CEDEC2017】Unityを使ったNintendo Switch™向けのタイトル開発・移植テクニック!!
例外設計における大罪 by Takuto Wada
例外設計における大罪例外設計における大罪
例外設計における大罪
Takuto Wada68.6K views
DockerとPodmanの比較 by Akihiro Suda
DockerとPodmanの比較DockerとPodmanの比較
DockerとPodmanの比較
Akihiro Suda48K views
MVPパターンによる設計アプローチ「あなたのアプリ報連相できてますか」 by U-dai Yokoyama
MVPパターンによる設計アプローチ「あなたのアプリ報連相できてますか」MVPパターンによる設計アプローチ「あなたのアプリ報連相できてますか」
MVPパターンによる設計アプローチ「あなたのアプリ報連相できてますか」
U-dai Yokoyama18K views
UniTask入門 by torisoup
UniTask入門UniTask入門
UniTask入門
torisoup14.2K views
【CEDEC2018】一歩先のUnityでのパフォーマンス/メモリ計測、デバッグ術 by Unity Technologies Japan K.K.
【CEDEC2018】一歩先のUnityでのパフォーマンス/メモリ計測、デバッグ術【CEDEC2018】一歩先のUnityでのパフォーマンス/メモリ計測、デバッグ術
【CEDEC2018】一歩先のUnityでのパフォーマンス/メモリ計測、デバッグ術
BuildKitの概要と最近の機能 by Kohei Tokunaga
BuildKitの概要と最近の機能BuildKitの概要と最近の機能
BuildKitの概要と最近の機能
Kohei Tokunaga4.6K views
Unityによるリアルタイム通信とMagicOnionによるC#大統一理論の実現 by Yoshifumi Kawai
Unityによるリアルタイム通信とMagicOnionによるC#大統一理論の実現Unityによるリアルタイム通信とMagicOnionによるC#大統一理論の実現
Unityによるリアルタイム通信とMagicOnionによるC#大統一理論の実現
Yoshifumi Kawai4.8K views

Similar to 【Unite Tokyo 2019】Understanding C# Struct All Things

パターンでわかる! .NET Coreの非同期処理 by
パターンでわかる! .NET Coreの非同期処理パターンでわかる! .NET Coreの非同期処理
パターンでわかる! .NET Coreの非同期処理Kouji Matsui
22.5K views116 slides
NetBSDのクロスビルドのしくみとインストール済みLive Imageの作成 by
NetBSDのクロスビルドのしくみとインストール済みLive Imageの作成NetBSDのクロスビルドのしくみとインストール済みLive Imageの作成
NetBSDのクロスビルドのしくみとインストール済みLive Imageの作成Izumi Tsutsui
7.2K views43 slides
C# 8.0 非同期ストリーム by
C# 8.0 非同期ストリームC# 8.0 非同期ストリーム
C# 8.0 非同期ストリーム信之 岩永
11.2K views63 slides
Ansible troubleshooting 101_2021 by
Ansible troubleshooting 101_2021Ansible troubleshooting 101_2021
Ansible troubleshooting 101_2021Hideki Saito
867 views42 slides
Visual Studioで始めるTypeScript開発入門 by
Visual Studioで始めるTypeScript開発入門Visual Studioで始めるTypeScript開発入門
Visual Studioで始めるTypeScript開発入門Narami Kiyokura
14.7K views144 slides
ICSをビルドしてみた by
ICSをビルドしてみたICSをビルドしてみた
ICSをビルドしてみたkinneko
580 views55 slides

Similar to 【Unite Tokyo 2019】Understanding C# Struct All Things(20)

パターンでわかる! .NET Coreの非同期処理 by Kouji Matsui
パターンでわかる! .NET Coreの非同期処理パターンでわかる! .NET Coreの非同期処理
パターンでわかる! .NET Coreの非同期処理
Kouji Matsui22.5K views
NetBSDのクロスビルドのしくみとインストール済みLive Imageの作成 by Izumi Tsutsui
NetBSDのクロスビルドのしくみとインストール済みLive Imageの作成NetBSDのクロスビルドのしくみとインストール済みLive Imageの作成
NetBSDのクロスビルドのしくみとインストール済みLive Imageの作成
Izumi Tsutsui7.2K views
C# 8.0 非同期ストリーム by 信之 岩永
C# 8.0 非同期ストリームC# 8.0 非同期ストリーム
C# 8.0 非同期ストリーム
信之 岩永11.2K views
Ansible troubleshooting 101_2021 by Hideki Saito
Ansible troubleshooting 101_2021Ansible troubleshooting 101_2021
Ansible troubleshooting 101_2021
Hideki Saito867 views
Visual Studioで始めるTypeScript開発入門 by Narami Kiyokura
Visual Studioで始めるTypeScript開発入門Visual Studioで始めるTypeScript開発入門
Visual Studioで始めるTypeScript開発入門
Narami Kiyokura14.7K views
ICSをビルドしてみた by kinneko
ICSをビルドしてみたICSをビルドしてみた
ICSをビルドしてみた
kinneko580 views
今から始める、Windows 10&新.NETへの移行戦略 by 信之 岩永
今から始める、Windows 10&新.NETへの移行戦略今から始める、Windows 10&新.NETへの移行戦略
今から始める、Windows 10&新.NETへの移行戦略
信之 岩永30.4K views
2014 1018 OSC-Fall Tokyo NETMF by Atomu Hidaka
2014 1018 OSC-Fall Tokyo NETMF2014 1018 OSC-Fall Tokyo NETMF
2014 1018 OSC-Fall Tokyo NETMF
Atomu Hidaka854 views
【学習メモ#1st】12ステップで作る組込みOS自作入門 by sandai
【学習メモ#1st】12ステップで作る組込みOS自作入門【学習メモ#1st】12ステップで作る組込みOS自作入門
【学習メモ#1st】12ステップで作る組込みOS自作入門
sandai10K views
Fukuoka.NET Conf 2018: 挑み続ける!Dockerコンテナによる ASP.NET Core アプリケーション開発事例 by Joni
Fukuoka.NET Conf 2018: 挑み続ける!Dockerコンテナによる ASP.NET Core アプリケーション開発事例Fukuoka.NET Conf 2018: 挑み続ける!Dockerコンテナによる ASP.NET Core アプリケーション開発事例
Fukuoka.NET Conf 2018: 挑み続ける!Dockerコンテナによる ASP.NET Core アプリケーション開発事例
Joni 232 views
Unityで使える C# 6.0~と .NET 4.6 by 信之 岩永
Unityで使える C# 6.0~と .NET 4.6Unityで使える C# 6.0~と .NET 4.6
Unityで使える C# 6.0~と .NET 4.6
信之 岩永21.4K views
ASP.NET vNextの全貌 by A AOKI
ASP.NET vNextの全貌ASP.NET vNextの全貌
ASP.NET vNextの全貌
A AOKI3.8K views
Ruby向け帳票ソリューション「ThinReports」の開発で知るOSSの威力 by ThinReports
Ruby向け帳票ソリューション「ThinReports」の開発で知るOSSの威力Ruby向け帳票ソリューション「ThinReports」の開発で知るOSSの威力
Ruby向け帳票ソリューション「ThinReports」の開発で知るOSSの威力
ThinReports6.6K views
ML Studio / CNTK ハンズオン資料の紹介と開発環境の構築手順 by Yoshitaka Seo
ML Studio / CNTK ハンズオン資料の紹介と開発環境の構築手順ML Studio / CNTK ハンズオン資料の紹介と開発環境の構築手順
ML Studio / CNTK ハンズオン資料の紹介と開発環境の構築手順
Yoshitaka Seo555 views
jQueryの先に行こう!最先端のWeb開発トレンドを学ぶ by Shumpei Shiraishi
jQueryの先に行こう!最先端のWeb開発トレンドを学ぶjQueryの先に行こう!最先端のWeb開発トレンドを学ぶ
jQueryの先に行こう!最先端のWeb開発トレンドを学ぶ
Shumpei Shiraishi4.3K views
Linux & Mac OS でも動く! ~ クロスプラットフォーム対応に見る ASP.NET Core 5 の可能性 ~ by Akira Inoue
Linux & Mac OS でも動く! ~ クロスプラットフォーム対応に見る ASP.NET Core 5 の可能性 ~Linux & Mac OS でも動く! ~ クロスプラットフォーム対応に見る ASP.NET Core 5 の可能性 ~
Linux & Mac OS でも動く! ~ クロスプラットフォーム対応に見る ASP.NET Core 5 の可能性 ~
Akira Inoue6.2K views

More from UnityTechnologiesJapan002

5分でわかる Sensor SDK by
5分でわかる Sensor SDK5分でわかる Sensor SDK
5分でわかる Sensor SDKUnityTechnologiesJapan002
3K views13 slides
10分でわかる Unityコンピュータービジョン by
10分でわかる Unityコンピュータービジョン10分でわかる Unityコンピュータービジョン
10分でわかる UnityコンピュータービジョンUnityTechnologiesJapan002
784 views23 slides
5分でわかる Unity Forma by
5分でわかる Unity Forma5分でわかる Unity Forma
5分でわかる Unity FormaUnityTechnologiesJapan002
399 views17 slides
ROSのロボットモデルでバーチャルロボット受肉する by
ROSのロボットモデルでバーチャルロボット受肉するROSのロボットモデルでバーチャルロボット受肉する
ROSのロボットモデルでバーチャルロボット受肉するUnityTechnologiesJapan002
563 views14 slides
Unityでロボットの教師データは作れる! by
Unityでロボットの教師データは作れる!Unityでロボットの教師データは作れる!
Unityでロボットの教師データは作れる!UnityTechnologiesJapan002
1.2K views22 slides
ARとUnity-Robotics-Hubの連携 by
ARとUnity-Robotics-Hubの連携ARとUnity-Robotics-Hubの連携
ARとUnity-Robotics-Hubの連携UnityTechnologiesJapan002
778 views13 slides

More from UnityTechnologiesJapan002(20)

建設シミュレータOCSの開発 / OCS・VTC on Unity におけるROS対応機能について by UnityTechnologiesJapan002
建設シミュレータOCSの開発 / OCS・VTC on Unity におけるROS対応機能について建設シミュレータOCSの開発 / OCS・VTC on Unity におけるROS対応機能について
建設シミュレータOCSの開発 / OCS・VTC on Unity におけるROS対応機能について
中国深センから盛り上がる、ソフトウェアフレンドリーなロボティクス by UnityTechnologiesJapan002
中国深センから盛り上がる、ソフトウェアフレンドリーなロボティクス中国深センから盛り上がる、ソフトウェアフレンドリーなロボティクス
中国深センから盛り上がる、ソフトウェアフレンドリーなロボティクス
集まれ!Dreamingエンジニア! 〜箱庭で紡ぎ出されるIoT/クラウドロボティクス開発の新しいカタチ〜 by UnityTechnologiesJapan002
集まれ!Dreamingエンジニア! 〜箱庭で紡ぎ出されるIoT/クラウドロボティクス開発の新しいカタチ〜集まれ!Dreamingエンジニア! 〜箱庭で紡ぎ出されるIoT/クラウドロボティクス開発の新しいカタチ〜
集まれ!Dreamingエンジニア! 〜箱庭で紡ぎ出されるIoT/クラウドロボティクス開発の新しいカタチ〜
BIMからはじまる異世界転生 ~Unity Reflect が叶える新しい建築の世界~ by UnityTechnologiesJapan002
BIMからはじまる異世界転生 ~Unity Reflect が叶える新しい建築の世界~BIMからはじまる異世界転生 ~Unity Reflect が叶える新しい建築の世界~
BIMからはじまる異世界転生 ~Unity Reflect が叶える新しい建築の世界~
【Unity道場 自動車編】Unityで実現する産業向けxRソリューション by UnityTechnologiesJapan002
【Unity道場 自動車編】Unityで実現する産業向けxRソリューション【Unity道場 自動車編】Unityで実現する産業向けxRソリューション
【Unity道場 自動車編】Unityで実現する産業向けxRソリューション
【Unity道場 自動車編】トヨタのxR活用で進める現場DXへの挑戦 ~UnityとHoloLens 2を用いて~ by UnityTechnologiesJapan002
【Unity道場 自動車編】トヨタのxR活用で進める現場DXへの挑戦 ~UnityとHoloLens 2を用いて~【Unity道場 自動車編】トヨタのxR活用で進める現場DXへの挑戦 ~UnityとHoloLens 2を用いて~
【Unity道場 自動車編】トヨタのxR活用で進める現場DXへの挑戦 ~UnityとHoloLens 2を用いて~
【Unity道場 自動車編】空間再現ディスプレイの概要と活用事例 by UnityTechnologiesJapan002
【Unity道場 自動車編】空間再現ディスプレイの概要と活用事例【Unity道場 自動車編】空間再現ディスプレイの概要と活用事例
【Unity道場 自動車編】空間再現ディスプレイの概要と活用事例
【Unity道場 自動車編】 リアルタイム3D技術が支えるデジタルツイン by UnityTechnologiesJapan002
【Unity道場 自動車編】 リアルタイム3D技術が支えるデジタルツイン【Unity道場 自動車編】 リアルタイム3D技術が支えるデジタルツイン
【Unity道場 自動車編】 リアルタイム3D技術が支えるデジタルツイン

Recently uploaded

SNMPセキュリティ超入門 by
SNMPセキュリティ超入門SNMPセキュリティ超入門
SNMPセキュリティ超入門mkoda
453 views15 slides
PCCC23:富士通株式会社 テーマ1「次世代高性能・省電力プロセッサ『FUJITSU-MONAKA』」 by
PCCC23:富士通株式会社 テーマ1「次世代高性能・省電力プロセッサ『FUJITSU-MONAKA』」PCCC23:富士通株式会社 テーマ1「次世代高性能・省電力プロセッサ『FUJITSU-MONAKA』」
PCCC23:富士通株式会社 テーマ1「次世代高性能・省電力プロセッサ『FUJITSU-MONAKA』」PC Cluster Consortium
45 views12 slides
SSH応用編_20231129.pdf by
SSH応用編_20231129.pdfSSH応用編_20231129.pdf
SSH応用編_20231129.pdficebreaker4
380 views13 slides
今、改めて考えるPostgreSQLプラットフォーム - マルチクラウドとポータビリティ -(PostgreSQL Conference Japan 20... by
今、改めて考えるPostgreSQLプラットフォーム - マルチクラウドとポータビリティ -(PostgreSQL Conference Japan 20...今、改めて考えるPostgreSQLプラットフォーム - マルチクラウドとポータビリティ -(PostgreSQL Conference Japan 20...
今、改めて考えるPostgreSQLプラットフォーム - マルチクラウドとポータビリティ -(PostgreSQL Conference Japan 20...NTT DATA Technology & Innovation
151 views42 slides
Keycloakの全体像: 基本概念、ユースケース、そして最新の開発動向 by
Keycloakの全体像: 基本概念、ユースケース、そして最新の開発動向Keycloakの全体像: 基本概念、ユースケース、そして最新の開発動向
Keycloakの全体像: 基本概念、ユースケース、そして最新の開発動向Hitachi, Ltd. OSS Solution Center.
89 views26 slides
Windows 11 information that can be used at the development site by
Windows 11 information that can be used at the development siteWindows 11 information that can be used at the development site
Windows 11 information that can be used at the development siteAtomu Hidaka
90 views41 slides

Recently uploaded(12)

SNMPセキュリティ超入門 by mkoda
SNMPセキュリティ超入門SNMPセキュリティ超入門
SNMPセキュリティ超入門
mkoda453 views
PCCC23:富士通株式会社 テーマ1「次世代高性能・省電力プロセッサ『FUJITSU-MONAKA』」 by PC Cluster Consortium
PCCC23:富士通株式会社 テーマ1「次世代高性能・省電力プロセッサ『FUJITSU-MONAKA』」PCCC23:富士通株式会社 テーマ1「次世代高性能・省電力プロセッサ『FUJITSU-MONAKA』」
PCCC23:富士通株式会社 テーマ1「次世代高性能・省電力プロセッサ『FUJITSU-MONAKA』」
SSH応用編_20231129.pdf by icebreaker4
SSH応用編_20231129.pdfSSH応用編_20231129.pdf
SSH応用編_20231129.pdf
icebreaker4380 views
今、改めて考えるPostgreSQLプラットフォーム - マルチクラウドとポータビリティ -(PostgreSQL Conference Japan 20... by NTT DATA Technology & Innovation
今、改めて考えるPostgreSQLプラットフォーム - マルチクラウドとポータビリティ -(PostgreSQL Conference Japan 20...今、改めて考えるPostgreSQLプラットフォーム - マルチクラウドとポータビリティ -(PostgreSQL Conference Japan 20...
今、改めて考えるPostgreSQLプラットフォーム - マルチクラウドとポータビリティ -(PostgreSQL Conference Japan 20...
Windows 11 information that can be used at the development site by Atomu Hidaka
Windows 11 information that can be used at the development siteWindows 11 information that can be used at the development site
Windows 11 information that can be used at the development site
Atomu Hidaka90 views
光コラボは契約してはいけない by Takuya Matsunaga
光コラボは契約してはいけない光コラボは契約してはいけない
光コラボは契約してはいけない
Takuya Matsunaga25 views
速習! PostgreSQL専用HAソフトウェア: Patroni(PostgreSQL Conference Japan 2023 発表資料) by NTT DATA Technology & Innovation
速習! PostgreSQL専用HAソフトウェア: Patroni(PostgreSQL Conference Japan 2023 発表資料)速習! PostgreSQL専用HAソフトウェア: Patroni(PostgreSQL Conference Japan 2023 発表資料)
速習! PostgreSQL専用HAソフトウェア: Patroni(PostgreSQL Conference Japan 2023 発表資料)
The Things Stack説明資料 by The Things Industries by CRI Japan, Inc.
The Things Stack説明資料 by The Things IndustriesThe Things Stack説明資料 by The Things Industries
The Things Stack説明資料 by The Things Industries
CRI Japan, Inc.76 views
PCCC23:東京大学情報基盤センター 「Society5.0の実現を目指す『計算・データ・学習』の融合による革新的スーパーコンピューティング」 by PC Cluster Consortium
PCCC23:東京大学情報基盤センター 「Society5.0の実現を目指す『計算・データ・学習』の融合による革新的スーパーコンピューティング」PCCC23:東京大学情報基盤センター 「Society5.0の実現を目指す『計算・データ・学習』の融合による革新的スーパーコンピューティング」
PCCC23:東京大学情報基盤センター 「Society5.0の実現を目指す『計算・データ・学習』の融合による革新的スーパーコンピューティング」

【Unite Tokyo 2019】Understanding C# Struct All Things

  • 2. About Speaker 2 — 河合 宜文 / Kawai Yoshifumi / @neuecc — Cysharp, Inc. – CEO/CTO — Microsoft MVP for Developer Technologies(C#) — 50以上のOSS公開(UniRx, MagicOnion, MessagePack for C#, etc..) — 株式会社Cysharp — 2019年9月, Cygamesの子会社として設立 — C#関連の研究開発/OSS/コンサルティングを行う — C#大統一理論(サーバー/クライアントともにC#で実装する)を推進
  • 3. https://github.com/Cysharp OSS for Unity – GitHub/Cysharp 3 UniTask ★288 Provides an efficient async/await integration to Unity. RuntimeUnitTestToolkit ★87 CLI/GUI Frontend of Unity Test Runner to test on any platforms. RandomFixtureKit ★16 Fill random/edge-case value to target type for unit testing. MagicOnion ★1240 Unified Realtime/API Engine for .NET Core and Unity. MasterMemory ★407 Embedded Typed Readonly In-Memory Document Database for .NET Core and Unity.
  • 4. https://github.com/neuecc OSS for Unity – GitHub/neuecc 4 LINQ-to-GameObject-for-Unity ★448 Traverse GameObject Hierarchy by LINQ. PhotonWire ★92 Typed Asynchronous RPC Layer for Photon. SerializableDictionary ★87 SerializableCollections for Unity. ReMotion ★27 Hyper Fast Reactive Tween Engine for Unity. UniRx ★3722 Reactive Extensions for Unity. MessagePack-CSharp ★2089 Extremely Fast MessagePack Serializer. ZeroFormatter ★1778 Infinitely Fast Deserializer. Utf8Json ★1352 Definitely Fastest JSON Serializer.
  • 6. 6 The Evolution of C# Struct
  • 7. MessagePack for C#(v2-preview) 7 public ref struct MessagePackWriter T 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];
  • 8. MessagePack for C#(v2-preview) 8 public ref struct MessagePackWriter T Deserialize<T>(in ReadOnlySequence<byte> byteSequence) Span<byte> bytes = stackalloc byte[36]; internal ref partial struct SequenceReader<T> where T : unmanaged, IEquatable<T> ref struct Span<T> = stackalloc in parameter where : unmanaged public ref byte GetPointer(int sizeHint) ref Entry v = ref entry[0]; ref return ref local
  • 11. What’s new struct features 11 C# 7.2 C# 7.3 C# 8.0 in modifier on parameter ref readonly modifier on method returns, local readonly struct declaration ref struct declaration ref extension method stackalloc to Span reassign ref local additional generics constraints(unmanage d, Enum, Delegate) stackalloc initializer access fixed field without pinning readonly method Disposable ref structs Unmanaged constructed types https://docs.microsoft.com/en-us/dotnet/csharp/whats-new/ C# 7.0 ref return statement ref local variables
  • 12. 12 Which Unity Version should we support? Unity Version C# Version .NET Version Unity 2017.4 6.0 .NET 3.5/.NET 4.6 Unity 2018.2 6.0 .NET 4.x/Standard 2.0 Unity 2018.3 7.3 .NET 4.x/Standard 2.0 Unity 2018.4 7.3 .NET 4.x/Standard 2.0 Unity 2019.1 7.3 .NET 4.x/Standard 2.0
  • 13. 13 Which Unity Version should we support? Unity Version C# Version .NET Version Unity 2017.4 6.0 .NET 3.5/.NET 4.6 Unity 2018.2 6.0 .NET 4.x/Standard 2.0 Unity 2018.3 7.3 .NET 4.x/Standard 2.0 Unity 2018.4 7.3 .NET 4.x/Standard 2.0 Unity 2019.1 7.3 .NET 4.x/Standard 2.0 2018.3以上一択、それ以下の バージョンはNot Supported
  • 14. 14 Which Unity Version should we support? Unity Version C# Version .NET Version Unity 2017.4 6.0 .NET 3.5/.NET 4.6 Unity 2018.2 6.0 .NET 4.x/Standard 2.0 Unity 2018.3 7.3 .NET 4.x/Standard 2.0 Unity 2018.4 7.3 .NET 4.x/Standard 2.0 Unity 2019.1 7.3 .NET 4.x/Standard 2.0 2018.3から以下の言語に関するdefineが使える CSHARP_7_3_OR_NEWER 以下のどちらか選んだほうが定義される NET_4_6 NET_STANDARD_2_0
  • 15. Struct is important for Performance! 15 — C# 7以降の急速なstruct強化はパフォーマンスのため — それは .NET Core でも、Unityでも — アプローチは異なれど、両者とも構造体をパフォーマンスのため活用している — 特にUnityの推すDOTS(Data Oriented Technology Stack)はstructの塊 — 今、全てを学び、備えよう public unsafe ref struct BlobBuilderArray<T> where T : struct public unsafe static ref T AsRef<T>(void* ptr) where T : struct public readonly struct BuildComponentDataToEntityLookupTask<TComponentData> : IDisposable where TComponentData : unmanaged, IComponentData, IEquatable<TComponentData> Unity ECSの中の C# 7.3表現
  • 16. 16 The Basic of C# Memory
  • 17. The Memory of C# 17 AppDomain(Managed) Thread Stack HeapThread Stack Unmanaged
  • 18. The Memory of C# 18 AppDomain(Managed) Thread Stack HeapThread Stack Unmanaged ローカル変数はスタック領域に格納される ヒープ領域に確保されたデータは GCの管理化に入る UnityではC#管理外のメモリを扱う こともよくある(特にDOTS)
  • 19. Let’s see memory layout 19 — SharpLabでメモリの中身を見よう! — https://sharplab.io/ — C# to IL, C# to C#, C# to ASMなど豊富な機能がある — Run と Inspectを組み合わせるとメモリの中身が見れる なお、組み込み型の場合、Unity(mono) とSharpLab(.NET Core)で中身が異なる ことがある場合に注意
  • 20. Struct Memory Int(4バイト)のX, Y, Z(12バイト)が 素直にメモリ上(スタック)に並ぶ
  • 22. Pass by Reference/Pass by Value 22 — C#はデフォルトは全て「値渡し」 — つまりコピーされます — ローカル変数への代入もコピー (T x) (ref T x) class 参照の値渡し 参照の参照渡し struct 値の値渡し 値の参照渡し
  • 24. 00 00 00 00 からそれっぽ いデータが入った気配
  • 25. 引き続き、 y = x で同じデ ータが追加で入ったっぽい
  • 28. int x int y int z コンパイル時(C# -> IL)の段 階で変数の置き場確保してお きます、的な
  • 29. Structの基本的な原則 29 — 全てはコピーに気をつける、ということ — クラスとの違い、ほとんどの問題はコピーにより引き起こされる — 大きなサイズの構造体を(基本的には)作らない – IntPtr.Size(4 or 8バイト)以上は参照渡しに比べて大きいということになる – それだと制限キツすぎなので、一般には16バイト以下ぐらいを目安に — 変更可能な構造体を(基本的には)作らない – コピーされることによって、変更したつもりが変更されない – 一度は悩むVector3変更されない問題
  • 30. Structの基本的な原則 30 — 全てはコピーに気をつける、ということ — クラスとの違い、ほとんどの問題はコピーにより引き起こされる — 大きなサイズの構造体を(基本的には)作らない – IntPtr.Size(4 or 8バイト)以上は参照渡しに比べて大きいということになる – それだと制限キツすぎなので、一般には16バイト以下ぐらいを目安に — 変更可能な構造体を(基本的には)作らない – コピーされることによって、変更したつもりが変更されない – 一度は悩むVector3変更されない問題 // position変えたつもりが変わらない! this.transform.position.Set(10f, 20f, 30f); // つまりこういうことだから this.transform.INTERNAL_get_position(out Vector3 value); value.Set(10f, 20f, 30f);
  • 31. Structの基本的な原則 31 — 全てはコピーに気をつける、ということ — クラスとの違い、ほとんどの問題はコピーにより引き起こされる — 大きなサイズの構造体を(基本的には)作らない – IntPtr.Size(4 or 8バイト)以上は参照渡しに比べて大きいということになる – それだと制限キツすぎなので、一般には16バイト以下ぐらいを目安に — 変更可能な構造体を(基本的には)作らない – コピーされることによって、変更したつもりが変更されない – 一度は悩むVector3変更されない問題 // position変えたつもりが変わらない! this.transform.position.Set(10f, 20f, 30f); // つまりこういうことだから this.transform.INTERNAL_get_position(out Vector3 value); value.Set(10f, 20f, 30f); positionがプロパティなのが悪い (フィールドならコピーの問題は起きない) が、これはtransformのデータの実体は アンマネージドメモリ側(UnityEngine)にある ためという、Unityならではの悩みでもある (C#/C++越境がどうしても抱える話)
  • 33. Box化との戦い 33 — ボックス化はnewと一緒 — むしろアンボックスの頻度的により悪い — ボックス化が避けられないなら最初からクラスで作ることも選択肢 — インターフェイスへのキャストに気をつける – ジェネリクスを使って回避していく – enumの場合はEnum(参照型)も同様 public static class BoxedInt { public static readonly object Zero = 0; public static readonly object One = 1; public static readonly object MinusOne = -1; } ある程度決まった値が頻繁にボッ クス化されるようなら、先に作っ ておいて使い回すという技
  • 34. 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になるもの だけ)でいいと思います
  • 35. Struct Layout and Padding 単純計算ではbyte(1) + long(8) + int(4) = 13ですが、 アラインメント調整のため、最長の8にそれぞれが合 わせられて8 * 3 = 24バイトの確保になっている
  • 36. Struct Layout and Padding StructLayoutやFieldOffsetによってレイアウ トはカスタマイズ可能。 structのデフォルトはSequential(宣言順) Autoに変えると、ZとXが詰められることで最 小の16バイトに縮む 参照型はstructと異なりデフォ ルトがAuto
  • 39. Zero Allocation foreach in List<T> 39 — foreachは .GetEnumerator -> while(MoveNext()) に変換される — (ただし配列の場合はコンパイル時にILでforに変換される) — つまり IEnumerator が生成されてヒープに確保されている? — されるようでされない var list = new List<int>() { 1, 2, 3 }; foreach (var item in list) { /* do anything */ }
  • 40. Mutable Struct is Evil but Useful 40 — 一時的な入れ物として使うものに向いてる 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じゃなくてもいい
  • 41. ref struct 41 — スタックにしか置けないという制約がref struct – 元々はSpan<T>(System.Memory, .NET Standard 2.0外部ライブラリ)のため – Span<T>は連続したメモリ領域のビューで、配列のように扱える(NativeArrayみたいな) – 今までポインタでしか扱えなかったstackallocを自然に扱えて便利 – しかしそれによってスタックにのみ確保したメモリ領域をヒープに移されると危険 – フィールドに置けない(ref structのfieldの場合のみ可)、ボクシングできない、インターフ ェイスを実装できない、ジェネリクスの型引数にできない、などの制約がある — ビュー的なものや一時的にしか使わない状態を持つものには適用しやすい – 制約が多いので無理に使おうとするとハマりますが…… Span<int> temp = stackalloc int[12];
  • 42. 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
  • 43. public void DoNanika(IEnumerable<int> idList) { var resources = idList.Select(x => Load(x)); // LINQの遅延実行により二回のLoadが走ってしまう // それを避けるために .ToList() するとそれはそれでListの無駄を感じる foreach (var item in resources) { /* nanika suru 1 */ } foreach (var item in resources) { /* nanika suru 2 */ } } public void DoNanika(IEnumerable<int> idList) { using var resources = idList.Select(x => Load(x)).ToTempList(); foreach (var item in resources) { /* nanika suru 1 */ } foreach (var item in resources) { /* nanika suru 2 */ } } usingだけで末尾で Disposeが便利 (C# 8.0から! Unityではまだ!) ここの中だけで使う一時配列はPoolから 取ってるのでアロケートなしで済んだ
  • 44. Avoid the copy, Everywhere 44 — 全て 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はネイティブメモリを使ったり色々 と固有の事情があるので一般論は適用で きない)
  • 45. static void Normal(Vector3 v3) { } static void In(in Vector3 v3) { } static void Ref(ref Vector3 v3) { } Avoid the copy, Everywhere 45 そこで登場するのが新しいキーワード “in” (このコード例自体には何の意味もないです)
  • 46. static void Normal(Vector3 v3) { } static void In(in Vector3 v3) { } static void Ref(ref Vector3 v3) { } Avoid the copy, Everywhere 46 Normal(v3); In(v3); Ref(ref v3); 呼び側のコード
  • 47. static void Normal(Vector3 v3) { } static void In(in Vector3 v3) { } static void Ref(ref Vector3 v3) { } Avoid the copy, Everywhere 47 ldloc.0 call Noraml ldloca.0 call In ldloca.0 call Ref Normal(v3); In(v3); Ref(ref v3); 呼び側のIL
  • 48. static void Normal(Vector3 v3) { } static void In(in Vector3 v3) { } static void Ref(ref Vector3 v3) { } Avoid the copy, Everywhere 48 ldloc.0 call Noraml ldloca.0 call In ldloca.0 call Ref 呼び方は普通と一緒なのに refと同じく参照渡しされる! Normal(v3); In(v3); Ref(ref v3); じゃあ全部 in でいいね! (にはならない)
  • 49. static void Normal(Vector3 v3) { _ = v3.magnitude; } static void In(in Vector3 v3) { _ = v3.magnitude; } static void Ref(ref Vector3 v3) { _ = v3.magnitude; } Avoid the copy, Everywhere 49 ldarga.0 call get_magnitude ldarg.0 ldobj stloc.0 ldloca.0 call get_magnitude ldarg.0 call get_magnitude 呼ばれ側のIL
  • 50. static void Normal(Vector3 v3) { _ = v3.magnitude; } static void In(in Vector3 v3) { _ = v3.magnitude; } static void Ref(ref Vector3 v3) { _ = v3.magnitude; } Avoid the copy, Everywhere 50 ldarga.0 call get_magnitude ldarg.0 ldobj stloc.0 ldloca.0 call get_magnitude ldarg.0 call get_magnitude 呼ばれ側のIL コピーされてる(防御的コピー) var v3_2 = v3; _ = v3_2.magnitude;
  • 51. static void Normal(Vector3 v3) { _ = v3.magnitude; _ = v3.magnitude; } static void In(in Vector3 v3) { _ = v3.magnitude; _ = v3.magnitude; } static void Ref(ref Vector3 v3) { _ = v3.magnitude; _ = v3.magnitude; } Avoid the copy, Everywhere 51 ldarga.0 call get_magnitude ldarg.0 ldobj stloc.0 ldloca.0 call get_magnitude ldarg.0 call get_magnitude 呼ばれ側のIL 二回呼べばコピーもそのまま二つ ldarga.0 call get_magnitude ldarg.0 ldobj stloc.0 ldloca.0 call get_magnitude ldarg.0 call get_magnitude
  • 52. 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は書き換わらないことが保証されるため防御的コピーが走らない
  • 53. 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 中でフィールドしか絶対触らないと力 強く言えるならナシではない よくわからないなら基本使わない
  • 54. readonly field(struct)の罠 54 — readonlyなfieldのstructにmutableな操作を行っても変更されない — よって、ミュータブルな操作を行うものはreadonly fieldにすべきではない — 原則的には「可能なものは」と言いたいところだけど Unityの場合、可能なものが多いので…… public class NantokaBehaviour : MonoBehaviour { public readonly Vector3 NanikanoVector3; public void Incr() { NanikanoVector3.Set( NanikanoVector3.x + 1f, NanikanoVector3.y + 1f, NanikanoVector3.z + 1f); } } 何回Incr呼んでも0, 0, 0のまま
  • 56. 改めてStructとは 56 — メモリを単純にマッピングした構造 – そのことだけ意識すれば、あとはやりたい放題できる [StructLayout(LayoutKind.Sequential, Size = 16)] public struct Empty16 { } public struct LongLong { public long X; public long Y; } どちらも連続して16バイトの 領域を確保しているだけ、とい う意味(に読める) インデックス0とインデックス 8にアクセスしやすくしている だけ(という風に読める)
  • 57. 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 .CompilerService s.Unsafeを利用、 Unityでも動きま すが別途参照は必 要、ポインタでや ってもいいです)
  • 58. 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があればそれでいい それらはほとんどメモリコピーで実現する
  • 59. Union 59 [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に変換するのに利用
  • 60. 改めてStructが要素の配列とは 60 — メモリにStructが単純に並んでいる X Y Z X Y Z X Y Z X Y ZVector3[] メモリをまるごとコピーするだけで 最速のシリアライズだよね説 (エンディアンは揃える) 実際MagicOnionで有効にすることが可 能(サーバーもC#なので直接メモリをぶ ん投げて受け取るのが簡単) ただしStructの中には参照型(String含 む)は含めないこと。ポイン タをコピー しても意味がない
  • 61. 61 public 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するだけ
  • 63. Span vs NativeArray 63 — Span<T> — System.Memory — .NET Standard 2.1では標準(現在は外部ライブラリが必要) — C# 7.2と統合されている — あらゆる連続したメモリ領域のビュー — 配列、stackalloc、ネイティブメモリ(ポインタ)、文字列(String) — NativeArray — Unity固有, 特にDOTSのキーパーツ — UnsafeUtility.Malloc で獲得するUnmanaged Memoryのビュー
  • 64. フレームワーク対応がないと意味がない 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から — というように、スムーズに全体的に統合されていくのはもうちょっとかな?
  • 67. Structを恐れない 67 — Structの使いこなし自体は、もはや必須 – Classを優先する牧歌的時代は終わった – 確かに罠は(いっぱい)あるが、難しい話ではない – 覚えるパターンが(ちょっと)多いだけで — そしてこの流れは戻らない – 言語強化から、フレームワークの抜本的変更まで、時代は既に来てる – ……とはいえ、じゃあいきなりめっちゃ使うかと言うと、それはまた別の話 – 使うべき時に使い、読めるようにするという、当たり前を大事にしよう