167
C# や .NET Framework ややややややや やや やや ++C++; // 未未未未未 C

C#や.NET Frameworkがやっていること

  • Upload
    -

  • View
    44.607

  • Download
    12

Embed Size (px)

DESCRIPTION

2014/3/1 Boost勉強会 #14 東京 にて https://sites.google.com/site/boostjp/study_meeting/study14 Boost勉強会なのに.NETの話で、1人だけ1時間(他の人は30分)。 本来、自分のペースでは4時間くらいかかってもおかしくない分量を1時間で。

Citation preview

Page 1: C#や.NET Frameworkがやっていること

C# や .NET Framework がやって

いること

岩永 信之

++C++; // 未確認飛行 C

Page 2: C#や.NET Frameworkがやっていること

自己紹介

C# でぐぐれ++C++; // 未確認飛行 C

こんなロゴのサイトの中の人プログラミングの魔導書 Vol.3 にも何か書いてるよ

Page 3: C#や.NET Frameworkがやっていること

今日の内容

• C# や .NET Framework がやっていること• どうしてそういう機能がある• ( 自前で実装するなら ) どうやればできる• ( 自前でやる上で ) 気を付ける必要がある点

Page 4: C#や.NET Frameworkがやっていること

趣旨

• 言語的に機能がないからって、やらなくていいわけじゃない。• C# とかが持ってる機能は必要だから追加されて

る• 自前で同様のことをやる方法も知ってて損ない

• 例えば…• C 言語でも OOP やってる人はやってた

• C 言語でも仮想関数テーブル自前で書いたり

Page 5: C#や.NET Frameworkがやっていること

注意

• 本来、 1 時間で話す内容じゃない• 置いてきぼる気満々• あんまり細かい説明しない(というかできない)

1 7 0ページ

Page 6: C#や.NET Frameworkがやっていること

まずは.NET Framework といえば

“Managed”

Page 7: C#や.NET Frameworkがやっていること

“Managed”

• .NET Framework の管理下にある• メモリ管理

• メモリ解放• 境界チェック• ドメイン分離

• 型の管理• メタデータ• JIT• PCL

Page 8: C#や.NET Frameworkがやっていること

Garbage Collectionメモリ リークをなくすために

Page 9: C#や.NET Frameworkがやっていること

.NET Framework の GC

• .NET Framework の GC は Mark & Sweep• 世代別 (3 世代、 Gen0 ~ 2)• Compaction あり• Background スレッドで並列実行

Page 10: C#や.NET Frameworkがやっていること

Mark & Sweep

• ルート†から参照をたどる

Mark

ヒープルート

† スタックや静的フィールド中のオブジェクト

Sweep

使用中オブジェクト

未使用オブジェクト

Page 11: C#や.NET Frameworkがやっていること

Compaction

• GC 時にオブジェクトの位置を移動

使用中オブジェクト

未使用オブジェクト

隙間をなくすように移動

後ろが丸ごと空くので次のメモリ確保が楽

Page 12: C#や.NET Frameworkがやっていること

Generation

• GC 時にオブジェクトの位置を移動

Gen1 Gen0

Gen0

しばらくここはノータッチ

この範囲でだけメモリ確保・

GC

オブジェクトの寿命統計的に• 短いものはとことん短く• 長いものはとことん長い

一度 GC かかったオブジェクトはしばらく放置

Page 13: C#や.NET Frameworkがやっていること

C++ での GC

• 通常、参照カウント• shared_ptr• 循環参照に気を付けて適宜 weak_ptr を

• Mark & Sweep GC ライブラリもあるけども• 型がはっきりしないので保守的にならざるを得な

い• すべての数値をポインターとみなして Mark

Page 14: C#や.NET Frameworkがやっていること

Mark & Sweep と参照カウント• 比較• 一長一短ある

Mark & Sweep 参照カウント

メモリ確保 ○ 末尾を見るだけ × 空いている場所を探す× カウント用の領域が追

加で必要

変数のコピー ○ ポインターのコピー × ポインターのコピーに加え、参照数のカウント アップ

メモリ解放 × Mark や Compactionに時間がかかる

× 負担が 1 か所に集中

○ カウントが 0 になった時に delete するだけ

Page 15: C#や.NET Frameworkがやっていること

Throughput

• トータルの性能 (throughput) は Mark & Sweep の方がいい

Mark & Sweep 参照カウント

メモリ確保 ○ 末尾を見るだけ × 空いている場所を探す× カウント用の領域が追

加で必要

変数のコピー ○ ポインターのコピー × ポインターのコピーに加え、参照数のカウント アップ

メモリ解放 × Mark や Compactionに時間がかかる

× 負担が 1 か所に集中

○ カウントが 0 になった時に delete するだけ

まとめてやる方がバラバラにやるより効率がいい

頻度が高い操作なのでここの負担が大きいと

全体の性能落ちる

特に、スレッド安全を求めるときついたかがインクリメントでも、 atomic 性保証するとそこそこの負担

参照局所性も高くなってキャッシュが効きやすい

Page 16: C#や.NET Frameworkがやっていること

短所の軽減

• 短所も、まったく打つ手がないわけじゃない

Mark & Sweep 参照カウント

メモリ確保 ○ 末尾を見るだけ × 空いている場所を探す× カウント用の領域が追

加で必要

変数のコピー ○ ポインターのコピー × ポインターのコピーに加え、参照数のカウント アップ

メモリ解放 × Mark や Compactionに時間がかかる

× 負担が 1 か所に集中

○ カウントが 0 になった時に delete するだけ

.NET 4以降、 Background スレッ

ド実行してる C++ 11 的には「move semantics活用してね」

Page 17: C#や.NET Frameworkがやっていること

自前メモリ管理との混在

• スコープが短いならスタックにとればいいのに• C# は class は必ずヒープ、 struct はスタックに

なる• 使い分けれない

• Mark & Sweep 管理領域内のポインターを管理外に渡すときには注意が必要• Compaction でメモリ移動しないように「ピン止め」が必要• 結構ガベージ コレクションのパフォーマンス落とす

Page 18: C#や.NET Frameworkがやっていること

おまけ

• Python とか• 参照カウントと Mark & Sweep の併用

• ○ 循環参照問題避けつつ、負担を 1 か所に集中させない

• × 性能的には悪いとこどり

• Go• 割当先を自動判別

• スコープ内で完結していたらスタックに• そうでなければヒープに• これはこれでスタックの浪費激しそうなんだけども…

• なので、可変長スタック持ってる

Page 19: C#や.NET Frameworkがやっていること

境界チェック意図しないメモリ領域にはアクセスさせない

Page 20: C#や.NET Frameworkがやっていること

配列の境界チェック

• .NET の配列は厳しい• 常に境界チェックしてる

• 要は buffer overrun防止• ちなみに JIT の最適化で、明らかに境界を侵さない

ものは境界チェックを消す

配列 a

OutOfRange

OutOfRange

a[-1] a[length+1]

Page 21: C#や.NET Frameworkがやっていること

unsafe

• C# にもポインターあるんだけども

var x = new[] { 1, 2, 3, 4, 5 };unsafe{ fixed(int* px = &x[0]) { Console.WriteLine(px[100]); }}

メモリ移動の防止(ピン止め)

範囲チェックなし

安全でないコードこの中でだけポインター利用可能

buffer overrun やりたい放題

Page 22: C#や.NET Frameworkがやっていること

unsafe でも制限付き

• class ( 参照型 ) はアドレス取れない• 仮想メソッド テーブル (vtable) とか入ってるし• 親クラス側のレイアウト変更の影響受けるし

class Class{ public int x; public int y;}

var c = new Class();fixed (void* p = &c) { }  // errorfixed (void* p = &c.x) { } // OKfixed (void* p = &c.y) { } // OK

ピン止め必須 • 値型のメンバーのアドレスは取れる

• オブジェクト自体のアドレスは取れない

Page 23: C#や.NET Frameworkがやっていること

unsafe でも制限付き

• struct ( 値型 ) はアドレス取れる• ただし、メンバーが全部値型の時のみ• ↑「 unmanaged 型」と呼ぶ

struct UnmanagedStruct{ public int x; public int y;}

var u = new UnmanagedStruct();void* p1 = &u;  // OKvoid* p2 = &u.x; // OKvoid* p3 = &u.y; // OK

メンバーが全部値型

無制限にアドレス取れる

Page 24: C#や.NET Frameworkがやっていること

unsafe でも制限付き

• struct ( 値型 ) であっても制限付き• メンバーに 1 つでも参照型を含むとダメ• ↑「managed 型」と呼ぶ

struct ManagedStruct{ public int x; public string y;}

var u = new ManagedStruct();void* p1 = &u;  // errorvoid* p2 = &u.x; // OKvoid* p3 = &u.y; // error

メンバーに参照型が 1

値型のメンバーのところだけはアドレス取れる

Page 25: C#や.NET Frameworkがやっていること

ポイント

• unsafe• 危険なことは基本認めない

• 面倒な追加の構文を要求• コンパイル オプションでも /unsafe の明記必須

• fixed• ガベージ コレクションとの兼ね合い

• Compaction阻害になるので注意

•「 Unmanaged 型」に限る• 実装依存な部分 (vtable とか)や、

型の定義側の変更が利用側に極力影響しないように

Page 26: C#や.NET Frameworkがやっていること

AppDomain実行環境の分離セキュリティ保証

Page 27: C#や.NET Frameworkがやっていること

コンポーネントの連携

•他のアプリの機能を自分のアプリから呼びたい• Word とか Excel とかのドキュメントを出力

• サーバー上に自分のアプリを配置したい• IIS 上に( ASP.NET )• SQL Server 上に

• ( 必ずしも ) 信用できない• セキュリティ保証• 参照先のクラッシュにアプリ / サー

バーが巻き込まれないように• コンポーネントのバージョン アップ

Page 28: C#や.NET Frameworkがやっていること

AppDomain

•単一プロセス内で、分離された複数の実行領域 (domain) を提供

•「分離」• plugin: 動的なロード / アンロード• security: AppDomain ごとに異なる権限付与• isolation: 別メモリ空間

AppDomain 1

AppDomain 2

プロセス

必ずしも信用できないコードを安全に、動的に呼び出し

Page 29: C#や.NET Frameworkがやっていること

ドメイン間には壁がある

• 別メモリ空間に分離されてる

AppDomain 1 AppDomain 2

互いに独立

Page 30: C#や.NET Frameworkがやっていること

ドメイン間の通信

•マーシャリング (marshaling)• marshal (司令官、案内係 ) の命令通りにしか壁を超えれない

AppDomain 1 AppDomain 2

• 司令がダメといったら通れない• 司令の指示通りの形式にいったん

シリアライズしないといけない

Page 31: C#や.NET Frameworkがやっていること

AppDomain 1 AppDomain 2

ドメイン間の通信

•ダメな例

これを B に渡したいとして

あるオブジェクト

Page 32: C#や.NET Frameworkがやっていること

AppDomain 1 AppDomain 2

ドメイン間の通信

•ダメな例

shallow copy なんてしようもんなら

A側のメモリへの参照が残る

Page 33: C#や.NET Frameworkがやっていること

AppDomain 1 AppDomain 2

ドメイン間の通信

• よい例

serializedeserializ

e

{ {1, 2}, {3, 4}, {5, 6}}

この辺りがマーシャリング

一度シリアライズ 必ず deep copy

Page 34: C#や.NET Frameworkがやっていること

.NET のマーシャリング

• 2種類• marshal by ref

• 参照を渡すんだけど、メモリ領域は直接触れない• メソッド越しにしか操作しちゃいけない• 「このメソッドを呼んでくれ」っていうメッセージだけ

がドメインを超えてわたって、実際の実行は相手側ドメインで

• marshal by value• 値をシリアライズして相手側に渡す• 規定では、 BinarySeralizer を使用

private フィールドまで含めてリフレクションで内部状態を取得してシリアライズ

Page 35: C#や.NET Frameworkがやっていること

.NET のマーシャリング (文字列 )• marshal by value の場合でも文字列は特殊扱

い• コピーしない• 参照をそのまま渡す

• immutable かつ range-check 付きに作ってある• COM (ネイティブ ) に対して文字列を渡す時すらこの方式(ただし、文字コード変換が不要な場合だけ)

Page 36: C#や.NET Frameworkがやっていること

AppDomain 1 AppDomain 2

その他のコスト

• いくらかコストが• 例えばライブラリの読み込み

Library X Library X

別ドメインに全く同じライブラリを読み込んでも、それぞれ別イメージが作られる

Global Assembly Cache (GAC) にある DLL だけは別GAC 中のは同じメモリ イメージが共有される

(マーシャリングのコスト以外にも )

Page 37: C#や.NET Frameworkがやっていること

C++ でも

• COM なんかはマーシャリングやってる•逆にいうと、 COM くらい仰々しいものが必

要• さもなくば、プロセス分離してプロセス間通信と

かやらなきゃいけない• 安全だけどかなり高負荷• RPC とか一時期流行ったけども

• ただの関数呼び出しに見えるコードで、内部的にプロセス間通信するとか

Page 38: C#や.NET Frameworkがやっていること

メタデータ型情報動的リンクでのバージョン管理

Page 39: C#や.NET Frameworkがやっていること

メタデータ

• メタデータとは• DLL にどういう型が含まれるかという情報• 外部のどういう DLL を参照しているかの情報• バージョン情報

• メタデータがあると• プログラミング言語をまたげる• 動的リンクでのバージョン管理

Page 40: C#や.NET Frameworkがやっていること

動的リンク

• ライブラリは共有する

アプリ A

アプリ B

ライブラリ X

ライブラリ Y

version 1

version 1

Page 41: C#や.NET Frameworkがやっていること

動的リンク

• ライブラリ単体での差し替え• セキュリティ ホールや、致命的なバグの修正

アプリ A

アプリ B

ライブラリ X

ライブラリ Y

version 2

version 1

更新不要差し替え

Page 42: C#や.NET Frameworkがやっていること

いまどきの事情

• ライブラリはアプリのパッケージに同梱• バージョニングいるの?

アプリ A ライブラリ X

ライブラリ Y アプリ B

ライブラリ X

ライブラリ Y

アプリ Aパッケージ アプリ Bパッケージ

別バイナリ

Page 43: C#や.NET Frameworkがやっていること

差分ダウンロード※ Windows ストア アプリはこの仕組み持ってるらしい

アプリ A ライブラリ X

ライブラリ Y

アプリ Aパッケージ version 1

version 1アプリ A ライブラリ X

ライブラリ Y

アプリ Aパッケージ version 2

version 2

version 1 version 1

version 1 version 1

ライブラリ X

差分

version 2

ダウンロード

アプリ A ver.1インストール機

バージョンアップ時

Page 44: C#や.NET Frameworkがやっていること

アプリ B

ライブラリ X

ライブラリ Y

アプリ間でライブラリ共有※ Windows ストア アプリはこの仕組み持ってるらしい

アプリ A ライブラリ X

ライブラリ Y

アプリ Aパッケージ

ライブラリ Y

アプリ Bパッケージ

差分

アプリ Aインストール機アプリ B

アプリ B インストール時

X 、 Y は同じものを共有(ハード リンク作るだけ)

version 1

version 1 version 1

version 1

Page 45: C#や.NET Frameworkがやっていること

メタデータの作成

• COM の頃• メタデータを自分で書いてた

( 自動化するツール使あるけど )• .tlb/.olb ファイル

• .NET Framework• .NET 自体ががメタデータの規格を持ってる• C# とかをコンパイルするだけで作られる

Page 46: C#や.NET Frameworkがやっていること

C++/CX

• C++ Component Extensions• マイクロソフトの C++拡張

C++/CX

素の標準C++ コード

COM コード

メタデータ(winmd)

コンパイル

C++ 内で完結して使う分にはオーバーヘッドな

COM を呼べる言語なら何からでも呼べる

.NET のメタデータと互換

.NET から簡単に呼べる

Page 47: C#や.NET Frameworkがやっていること

JIT(Just-in-Time compile)バージョン アップ時の変更の影響を吸収

Page 48: C#や.NET Frameworkがやっていること

中間コードと JIT コンパイル

• .NET Framework の中間言語

• 高級言語のコンパイラーを作る人と、 CPU ごとの最適化する人の分業化• セキュリティ チェックしやすくなったり• 動的リンク時に、コード修正の影響を JIT で吸収

高級言語(C# など )

中間言語(IL)

ネイティブコード

ビルド時にコンパイル

Just-in-Timeコンパイル

JIT である必要ないLLVM とかでも中間言語介してコンパイルして

ストア審査ではじくとか他にも手段はある

Page 49: C#や.NET Frameworkがやっていること

• (C# で ) こんな型があったとして

• 整数のフィールドを 3 つ持つ

public struct Point{

public int X;public int Y;public int Z;

}

Page 50: C#や.NET Frameworkがやっていること

• こんなメソッドを書いたとする

• フィールドの掛け算

static int GetVolume(Point p){

return p.X * p.Y * p.Z;}

Page 51: C#や.NET Frameworkがやっていること

IL

• C# コンパイル結果の IL.method private hidebysig static int32 GetVolue(valuetype Point p) cil managed{ .maxstack 8 IL_0000: ldarg.0 IL_0001: ldfld int32 Point::X IL_0006: ldarg.0 IL_0007: ldfld int32 Point::Y IL_000c: mul IL_000d: ldarg.0 IL_000e: ldfld int32 Point::Z IL_0013: mul IL_0014: ret}

型とかフィールドの名前がそのまま

残ってる

型情報メタデータ

Page 52: C#や.NET Frameworkがやっていること

ネイティブ コード

• JIT結果 (x64 の場合 )

push ebp mov ebp,esp cmp dword ptr ds:[5011058h],0 je 00FE2A01 call 74B7AEA8 mov eax,dword ptr [ebp+8] lea edx,[ebp+8] imul eax,dword ptr [edx+4] lea edx,[ebp+8] imul eax,dword ptr [edx+8] pop ebp ret 0Ch

4 とか 8 とかの数値に

型情報は残らない

Page 53: C#や.NET Frameworkがやっていること

メモリ レイアウト

• この 4 とか 8 の意味

public struct Point{

public int X;public int Y;public int Z;

}

Point

X

Y

Z

4 バイト

8 バイト

※レイアウトがどうなるかは環境依存

Page 54: C#や.NET Frameworkがやっていること

メモリ レイアウト

• この 4 とか 8 の意味

public struct Point{

public int X;public int Y;public int Z;

}

Point

X

Y

Z

4 バイト

8 バイト

※レイアウトがどうなるかは環境依存

IL の時点までは名前で参照してる

ネイティブ コードはレイアウトを見て数値で参照してる

Page 55: C#や.NET Frameworkがやっていること

数値でのフィールド参照

• C# で擬似的に書くとstatic int GetVolume(Point p){

return p.X * p.Y * p.Z;}

var pp = (byte*)&p;var x = *((int*)pp);var y = *((int*)(pp + 4));var z = *((int*)(pp + 8));return x * y * z;

4 とか 8 とかの数値に

※これ、一応 C# として有効なコード (unsafe)

Page 56: C#や.NET Frameworkがやっていること

変更してみる

• 大して影響しなさそうなほんの些細な変更をしてみる

public struct Point{

public int X;public int Y;public int Z;

}

public struct Point{

public int X;public int Z;public int Y;

}フィールドの順序変更

Page 57: C#や.NET Frameworkがやっていること

その結果起きること

• メモリ レイアウトが変わる※

Point

X

Y

Z

Point

X

Z

Y

※ この例(フィールド変更)以外でも、仮想メソッド テーブルとかいろいろレイアウトが変わるものがある

Page 58: C#や.NET Frameworkがやっていること

IL レベルでの影響

•影響なし

IL_0000: ldarg.0IL_0001: ldfld int32 Point::XIL_0006: ldarg.0IL_0007: ldfld int32 Point::YIL_000c: mulIL_000d: ldarg.0IL_000e: ldfld int32 Point::ZIL_0013: mulIL_0014: ret

名前で参照してるんだから特に影響ないJIT が吸収してくれる

Page 59: C#や.NET Frameworkがやっていること

ネイティブ レベルでの影響

• ここで影響が出るpush ebp mov ebp,esp cmp dword ptr ds:[5011058h],0 je 00FE2A01 call 74B7AEA8 mov eax,dword ptr [ebp+8] lea edx,[ebp+8] imul eax,dword ptr [edx+4] lea edx,[ebp+8] imul eax,dword ptr [edx+8] pop ebp ret 0Ch

8

4更新が必要

利用側の再コンパイルが必要

ライブラリ側だけの差し替えじゃダメ

Page 60: C#や.NET Frameworkがやっていること

ただし…

• この役割に焦点を当てるなら…• 毎回毎回 JIT する必要ない• 全部が全部 IL な必要ない

Page 61: C#や.NET Frameworkがやっていること

Ngen

• Ngen.exe• Native Image Generator• IL を事前にネイティブ化するためのツール• 自前管理が必要

• アプリのインストーラー※とかを作って明示的に呼び出し

• 参照しているライブラリが更新された時には呼びなおす必要あり •かなり面倒なのでアプリを

Ngen することはめったにない• .NET 自体が標準ライブラリ

の高速化のために使ってる※ 要するに、 JIT の負担を起動時じゃなくてインストール時に前倒しする

Page 62: C#や.NET Frameworkがやっていること

Auto-Ngen

• .NET Framework 4.5以降なら• Ngen が Windows サービスとして常に動いてる

• アイドル時に動作• 利用頻度の高いものを自動的に Ngen

• デスクトップ アプリの場合は GAC アセンブリのみ• Windows ストア アプリの場合はすべてのアセンブリ

•よく使うアプリの起動はだいぶ早くなる•インストール直後の起動は相変わらず遅

Page 63: C#や.NET Frameworkがやっていること

MDIL (ネイティブのおさらい )•おさらい : ネイティブ コードだと

push ebp mov ebp,esp cmp dword ptr ds:[5011058h],0 je 00FE2A01 call 74B7AEA8 mov eax,dword ptr [ebp+8] lea edx,[ebp+8] imul eax,dword ptr [edx+4] lea edx,[ebp+8] imul eax,dword ptr [edx+8] pop ebp ret 0Ch

参照しているライブラリのレイアウトが変

わった時に再コンパイルが必要

Page 64: C#や.NET Frameworkがやっていること

MDIL (部分的にネイティブ化 )• じゃあ、こんな形式があればいいんじゃ?

push ebp mov ebp,esp cmp dword ptr ds:[5011058h],0 je 00FE2A01 call 74B7AEA8 mov eax,dword ptr [ebp+8] lea edx,[ebp+8] imul eax,dword ptr [edx+4] lea edx,[ebp+8] imul eax,dword ptr [edx+8] pop ebp ret 0Ch

int32 Point::Xint32

Point::Yint32

Point::Z

ほぼネイティブ レイアウトのとこ

ろだけ抽象的に型情報を残しておく

Page 65: C#や.NET Frameworkがやっていること

MDIL (Compile in the Cloud)• 実際、 Windows Phone のストアでは

そういう形式が使われている : MDIL(Machine Dependent Intermediate Language)C#コード

IL

MDIL

ネイティブ コード

C# コンパイラー

MDIL コンパイラー

リンカー

開発環境で IL にコンパイル

Windows ストア サーバー上で IL を MDIL化(Compile in the Cloud)

Windows Phone 実機上ではレイアウトの解決 ( リンク ) だけ行う

•インストール直後の起動も安心

Page 66: C#や.NET Frameworkがやっていること

C++ だと

• 動的にやろうと思ったら、結局、 COM とかWinRT になってく• いったん QueryInterface みたいなの挟む• 結構でっかいオーバーヘッド†

† WinRT はオーバーヘッド解消の仕組みも持ってて、C++ で完結してる限りには普通の仮想関数呼び出しになる他言語から使う場合には COM アクセスになる

Page 67: C#や.NET Frameworkがやっていること

C++ だと

• 静的な話で言うと

ヘッダーファイルを変更したら利用側の再コンパイルが必要

struct Point{ double x; double y;};

Point.h

struct Point{ double radius; double theta;};

要 再コンパイル

Page 68: C#や.NET Frameworkがやっていること

なので

• setter/getter 使う• pimpl 使う

struct PointImpl; class Point{public: double get_x(); double get_y();private: PointImpl* impl;};

Point.h

struct PointImpl{ double x; double y;}; double Point::get_x(){  return impl->x;}double Point::get_y(){  return impl->y;}

Point.cpp ( 実装その 1)

Page 69: C#や.NET Frameworkがやっていること

なので

• setter/getter 使う• pimpl 使う

struct PointImpl; class Point{public: double get_x(); double get_y();private: PointImpl* impl;};

Point.h

struct PointImpl{ double radius; double theta;}; double Point::get_x(){  return impl->radius * cos(impl->theta);}double Point::get_y(){  return impl->radius * sin(impl->theta);}

Point.cpp ( 実装その 2)

変更なし

こっちだけの変更なら、利用側の再コンパイル不要

Page 70: C#や.NET Frameworkがやっていること

Portable Class Library複数の「標準ライブラリ」いろんな実行環境の共通部分

Page 71: C#や.NET Frameworkがやっていること

標準ライブラリ

•マイクロソフト製品だけでも…

デスクトップクライアント アプリ

Phone/ タブレットクライアント アプリ

サーバー アプリ

共通部分

この共通部分だけを「標準」にすべき?

Page 72: C#や.NET Frameworkがやっていること

標準ライブラリ

• まして今、 Xamarin (Mono)• Xamarin.iOS 、 Xamarin.Android

WindowsデスクトップWindows

タブレット /Phone

Linuxサーバー

この共通部分だけを「標準」にすべき?

Windowsサーバー

iOS

Android

Page 73: C#や.NET Frameworkがやっていること

.NET Framework と Mono

C#

VB

F#

C#

CLR

Monoランタイム

.NET Fullプロファイル

.NET Coreプロファイル

.NET Full( サブセッ

ト )iOS向け

プロファイル

Android向けプロファイル

.NETFramework

Mono

コンパイラー 実行環境 ライブラリ

実行環境に互換性があっても 標準で使える

ライブラリが違う

Page 74: C#や.NET Frameworkがやっていること

どこでも使えそうに見えても…• 例えばファイル システム• 最近の Windows はいろんなプロパティを持って

る• 画像ファイルなんかだと :

サムネイル画像、撮影日時、画像サイズ、タグ• 検索インデックスも張ってる

• ストア アプリだとファイル システムすら制限下• ユーザーの許可がないファイルには触れない

こういうものも標準に含めたいか?

一番制限がきつい環境に合わせて標準ライブラリを作るべき?

Page 75: C#や.NET Frameworkがやっていること

汎用 VS 特定環境どこでも動く• 最

大公約数

• 動作保証に時間がかかる

特定環境で動く• 高

機能

• 早く提供できる

Page 76: C#や.NET Frameworkがやっていること

汎用 VS 特定環境どこでも動く• 最

大公約数

• 動作保証に時間がかかる

特定環境で動く• 高

機能

• 早く提供できる

C++ だと• 標準化にかかってる時間• ライブラリのソースコードをとってきたは

いいけど自分の環境での動作確認も大変

Page 77: C#や.NET Frameworkがやっていること

Portable Class Library

• 実行環境ごとに別の「標準ライブラリ」メタデータを用意• ライブラリ側でどの

実行環境をターゲットにするか選ぶ

実行環境ごとに別メタデータを提供

ターゲット選択

Page 78: C#や.NET Frameworkがやっていること

Portable Class Library

• 例 : 2 環境

共通部分

Page 79: C#や.NET Frameworkがやっていること

Portable Class Library

• 例 : 3 環境

共通部分

Page 80: C#や.NET Frameworkがやっていること

C++

• ターゲット広すぎてより一層大変だけど• メタデータ持ってない言語でやるのも厳しい• #ifdef?

Page 81: C#や.NET Frameworkがやっていること

さてもうすでに約 80 ページ

Page 82: C#や.NET Frameworkがやっていること

ここまで / ここから

• ここまで: .NET のインフラ的なところ• いわゆる“Managed”• C# 1.0 の頃からある基本機能

• 所詮、既存言語を Managed にした程度

• ここから: C# 2.0以降の追加機能

Page 83: C#や.NET Frameworkがやっていること

C# の歴史

C# 1.0• Man

aged

C# 2.0• Generi

cs

C# 3.0• LINQ

C# 4.0• Dynamic

C# 5.0• Async• WinRT

※VB 7 ~ 11 の歴史でもある

Page 84: C#や.NET Frameworkがやっていること

ジェネリックC# 2.0

C++ で言うところの template

C++ template と .NET Generics の違い

Page 85: C#や.NET Frameworkがやっていること

C++ template と C# genericsC++ template C# generics

高機能マクロみたいなもので、コンパイル時に全部展開される

.NET の中間言語 / 型情報レベルで対応JIT 時に展開

vector<T> と vector<U> で別コードが生成される

List<T> と List<U> で同じコードを共有 ( 参照型の場合 )

すべてヘッダーに書く必要がある変更すると利用側も再コンパイル

内部を変更しても (public なところが変わらない限り )利用側に影響なしコンパイル時に

すべてやる方針メタデータ /JIT で話した要件• 言語をまたげる• 動的リンク• バージョン管理• 利用側に影響与えない

.NET 的にはこれを守るのが最重要

Page 86: C#や.NET Frameworkがやっていること

もし C++ で generics やるなら• いったん汎用クラスを作るclass void_vector{public: void* operator[](int index); void add(void* item); void remove(void* item);… 後略 …

いったん void*な汎用クラスを

作る

( 動的には無理だけど、せめて静的に )

Page 87: C#や.NET Frameworkがやっていること

もし C++ で generics やるなら• キャストだけ template でtemplate<typename T>class vector<T>{private: void_vector impl;public: T* operator[](int index) { return static_cast<T*>(impl[index]); } void add(T* item) { impl.add(item); } void remove(T* item) { impl.remove(item); }};

キャストするだけの薄い template クラスを作る

要素の型が保証されるので static_cast でOK

さっき作った void* のクラス

( 動的には無理だけど、せめて静的に )

Page 88: C#や.NET Frameworkがやっていること

もし C++ で generics やるなら• このやり方 (汎用クラス +template でキャス

ト ) なら• vector<T> と vector<U> でコード共有可能

• バイナリ サイズ小さくできる• (pimpl を併用すれば ) 内部実装を変更しても、利

用側の再コンパイル不要• コンパイル時間だいぶ短くできる

Page 89: C#や.NET Frameworkがやっていること

とはいえ、いろいろ窮屈

• generics では、インターフェイス制約かけないとメソッドすら呼べないstatic Type Max<Type>(Type a, Type b){ return a.CompareTo(b) > 0 ? a : b;}

static Type Max<Type>(Type a, Type b) where Type : IComparable{ return a.CompareTo(b) > 0 ? a : b;}

コンパイル エラーそんなメソッド知らな

い正しくは

インターフェイス制約

IComparable.CompareTo

Page 90: C#や.NET Frameworkがやっていること

とはいえ、いろいろ窮屈

• 特に困るのが演算子使えないこと• 実体は静的メソッドなので

static T Sum<T>(T[] array){

T sum = default(T);foreach (var x in array) sum += x;return sum;

} コンパイル エラー演算子定義されてない

Page 91: C#や.NET Frameworkがやっていること

とはいえ、いろいろ窮屈

• dynamic でごまかせなくはないけども…• 性能出ないので最後の手段

static T Sum<T>(T[] array){

dynamic sum = default(T);foreach (var x in array) sum += x;return sum;

} 実行時コード生成で+演算子が呼ばれる

Page 92: C#や.NET Frameworkがやっていること

C++/CX

• C++/CX はジェネリックを持ってる• generic キーワード• メタデータ (winmd) 上は .NET のジェネリックと互換• かなり制限きつい

• 実装 (generic なクラス ) は public にできない• private/internal なクラスは template で展開• ユーザー定義のインターフェイス、デリゲートも

generic な場合は public にできない

Page 93: C#や.NET Frameworkがやっていること

イテレーターC# 2.0

イテレーター生成用の構文

Page 94: C#や.NET Frameworkがやっていること

イテレーター ブロック

• C++ 的な意味のイテレーターstd::vector<int> v{ 1, 2, 3, 4 }; for (auto p = v.begin(); p != v.end(); ++p)

std::cout << *p << std::endl;

こういうの

• 使う側 (for ループ側 ) は楽でいいんだけども• 実装側 (vector_iterator の中身 ) はめんどくさい

これを楽にする

の生成を楽にする

Page 95: C#や.NET Frameworkがやっていること

• substring の列挙

static IEnumerable<string> GetSubstrings(string s){    for (var len = s.Length; len >= 1; len--)        for (var i = 0; i <= s.Length - len; i++)            yield return s.Substring(i, len);}

foreach (var x in GetSubstrings("abcd"))    Console.WriteLine(x);

実装側

使う側

イテレーター ブロック†(= yield return を持つ関数ブロッ

ク )

Page 96: C#や.NET Frameworkがやっていること

内部実装 ( 全体像 )

• クラス生成

class SubstringEnumerable : IEnumerator<string>, IEnumerable<string>{    readonly string _s;    int _len;    int _i;    int _state = 0;     public SubstringEnumerable(string s) { _s = s; }     public string Current { get; private set; }     public bool MoveNext()    {        if (_state == 1) goto STATE1;        if (_state == -1) goto END;        _state = 1;        _len = _s.Length;    LOOP1BEGIN: ;        if (!(_len >= 1)) goto LOOP1END;        _i = 0;    LOOP2BEGIN: ;        if (!(_i <= _s.Length - _len)) goto LOOP2END;        _state = 1;        Current = _s.Substring(_i, _len);        return true;    STATE1: ;        _i++;        goto LOOP2BEGIN;    LOOP2END: ;        _len--;        goto LOOP1BEGIN;    LOOP1END: ;        _state = -1;    END: ;        return false;    }     public void Reset() { throw new NotImplementedException(); }    public void Dispose() { }    object IEnumerator.Current { get { return Current; } }     public IEnumerator<string> GetEnumerator()    {        if(_state == 0) return this;        else return new SubstringEnumerable(_s).GetEnumerator();    }     IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); }}

static IEnumerable<string> GetSubstrings(string s){    for (var len = s.Length; len >= 1; len--)        for (var i = 0; i <= s.Length - len; i++)            yield return s.Substring(i, len);}

Page 97: C#や.NET Frameworkがやっていること

内部実装 ( ローカル変数 )

• ローカル変数 → フィールド

class SubstringEnumerable : IEnumerator<string>, IEnumerable<string>{    readonly string _s;    int _len;    int _i;    int _state = 0;     public SubstringEnumerable(string s) { _s = s; }     public string Current { get; private set; }     public bool MoveNext()    {        if (_state == 1) goto STATE1;        if (_state == -1) goto END;        _state = 1;        _len = _s.Length;    LOOP1BEGIN: ;        if (!(_len >= 1)) goto LOOP1END;        _i = 0;    LOOP2BEGIN: ;        if (!(_i <= _s.Length - _len)) goto LOOP2END;        _state = 1;        Current = _s.Substring(_i, _len);        return true;    STATE1: ;        _i++;        goto LOOP2BEGIN;    LOOP2END: ;        _len--;        goto LOOP1BEGIN;    LOOP1END: ;        _state = -1;    END: ;        return false;    }     public void Reset() { throw new NotImplementedException(); }    public void Dispose() { }    object IEnumerator.Current { get { return Current; } }     public IEnumerator<string> GetEnumerator()    {        if(_state == 0) return this;        else return new SubstringEnumerable(_s).GetEnumerator();    }     IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); }}

static IEnumerable<string> GetSubstrings(string s){    for (var len = s.Length; len >= 1; len--)        for (var i = 0; i <= s.Length - len; i++)            yield return s.Substring(i, len);}

Page 98: C#や.NET Frameworkがやっていること

内部実装 (yield return)

• yield return → 状態記録、 return 、 case ラベル• 中断と再開         if (_state == 1) goto STATE1;

        if (_state == -1) goto END;        _state = 1;        _len = _s.Length;    LOOP1BEGIN: ;        if (!(_len >= 1)) goto LOOP1END;        _i = 0;    LOOP2BEGIN: ;        if (!(_i <= _s.Length - _len)) goto LOOP2END;        _state = 1;        Current = _s.Substring(_i, _len);        return true;    STATE1: ;        _i++;        goto LOOP2BEGIN;    LOOP2END: ;        _len--;        goto LOOP1BEGIN;    LOOP1END: ;        _state = -1;    END: ;        return false;    }     public void Reset() { throw new NotImplementedException(); }    public void Dispose() { }    object IEnumerator.Current { get { return Current; } }     public IEnumerator<string> GetEnumerator()    {        if(_state == 0) return this;        else return new SubstringEnumerable(_s).GetEnumerator();    }     IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); }}

static IEnumerable<string> GetSubstrings(string s){    for (var len = s.Length; len >= 1; len--)        for (var i = 0; i <= s.Length - len; i++)            yield return s.Substring(i, len);}

Page 99: C#や.NET Frameworkがやっていること

switch(_state){case 0:    for (_len = _s.Length; _len >= 1; _len--)        for (_i = 0; _i <= _s.Length - _len; _i++)        {            _state = 1;            Current = _s.Substring(_i, _len);            return true;            case 1:;        }    _state = -1;}

内部実装 (yield return)

• yield return の部分、意味合いとしては†

static IEnumerable<string> GetSubstrings(string s){    for (var len = s.Length; len >= 1; len--)        for (var i = 0; i <= s.Length - len; i++)            yield return s.Substring(i, len);}

yield return以外の場所はほぼ同じ

yield return x;

_state = 1;Current = x;return true;case 1:;

† for ループ内にラベル張れないからさっきみたいな複雑なコードになるけども

switch で囲う

Page 100: C#や.NET Frameworkがやっていること

内部実装 ( 中断と再開 )

• yield return の部分、意味合いとしては• 中断と再開

yield return x;

_state = 1;Current = x;return true;case 1:;

状態の記録

現在の値の保持

復帰用のラベル

Page 101: C#や.NET Frameworkがやっていること

機械的な置き換えでできる

• C++ でも割と簡単class substring_iterator{private:    const std::string& _s;    int _len;    int _i;    std::string _current;    int _state;public:    substring_iterator(const std::string& s) : _s(s), _state(0) {}    std::string current() { return _current; }    bool move_next()    {        switch (_state)        {        case 0:;            for (_len = _s.length(); _len >= 1; _len--)                for (_i = 0; _i <= _s.length() - _len; _i++)                {                    _state = 1;                    _current = _s.substr(_i, _len);                    return true;        case 1:;                }            _state = -1;        default:;        }        return false;    }};

goto とか case ラベルの制限がゆるいので、むしろ簡

Page 102: C#や.NET Frameworkがやっていること

機械的置き換えといえば

•マクロを定義#define BEGIN_ITERATOR\    switch(_state)\ {\    case 0:

#define END_ITERATOR\    _state = -1;\    default:;\ }\    return false;

#define YIELD(STATE, VALUE)\    _state = STATE;\    _current = VALUE;\    return true;\    case STATE:;

Page 103: C#や.NET Frameworkがやっていること

機械的置き換えといえば

•マクロを使ってbool move_next(){    BEGIN_ITERATOR    for (_len = _s.length(); _len >= 1; _len--)        for (_i = 0; _i <= _s.length() - _len; _i++)        {            YIELD(1, _s.substr(_i, _len))        }    END_ITERATOR}

Page 104: C#や.NET Frameworkがやっていること

他の言語だと

•最近の言語は結構似た機能持ってる• ジェネレーター (generator) って呼ばれることが多い• 実装方式 :

• スタック丸ごとキャプチャしてしまうもの• 中断時に記録、再開時に復元

• 式を継続渡しスタイル (CPS) に変換してしまうもの• スレッド使う

• concurrent queue 使って、 yield return のところでenqueue

• さすがに性能的に論外だけども

Page 105: C#や.NET Frameworkがやっていること

ちなみに

• コーディング面接で有名な某社の社員さん曰く、

• 例に使ったの substring 列挙も割とよく出てくるパターン• 中断と再開って大事

アルゴリズムの問題はかなりの割合、イテレーターを使うとあっさり書ける

「」

Page 106: C#や.NET Frameworkがやっていること

C++ 1y

• C++ にもジェネレーターが載るかも• C++ 17 に向けて標準化案出てる• C++/CX向けの実装を元に MS が提案

• async/await のついで

sequence<int> range(int low, int high) resumable{ for(int i = low; i <= high; ++i) { yield i; }}

Page 107: C#や.NET Frameworkがやっていること

LINQ(Language Integrated Query)C# 3.0

データ処理の直行化

Page 108: C#や.NET Frameworkがやっていること

LINQ

• データ処理を言語統合• ほとんどはライブラリで実現

• STL の <algorithm> とか Boost.Range の Adaptersみたいなもの

var source = new[] { 1, 2, 3, 4, 5 };var filtered = source    .Where(x => x <= 3)  // 1, 2, 3    .Select(x => x * x); // 1, 4, 9

System.Linq.Enumerable.Where メソッドと

System.Linq.Enumerable.Select メソッド

が呼ばれるだけ

Page 109: C#や.NET Frameworkがやっていること

データ処理の直行化

• まあ、データの入力、加工、出力は分けましょうという話

ユーザー入力からConsole.Read…

配列から{ 1, 2, 3, … }

データベースからSELECT x FROM t

ファイルからFile.Read…

1, 2, 3変換x => x * x1, 4, 9

1, 2, 3選択x => x < 31, 2

1, 2, 3グループ化x => x % 2{1, 3},

{2}コンソールにConsole.Write…

配列にToArray()

データベースにINSERT INTO t

ファイルにFile. Write…

入力 加工 出力

Page 110: C#や.NET Frameworkがやっていること

データ処理の直行化

•掛け算を足し算に

ユーザー入力からConsole.Read…

配列から{ 1, 2, 3, … }

データベースからSELECT x FROM t

ファイルからFile.Read…

1, 2, 3変換x => x * x1, 4, 9

1, 2, 3選択x => x < 31, 2

1, 2, 3グループ化x => x % 2{1, 3},

{2}コンソールにConsole.Write…

配列にToArray()

データベースにINSERT INTO t

ファイルにFile. Write…

入力 加工 出力

Lパターン Mパターン Nパターン

分けて作らないと L×M×N 通りのパターン

分けて作ると L+M+N 通り

Page 111: C#や.NET Frameworkがやっていること

関連する C# の言語機能

• C# 3.0• ラムダ式• 匿名型• 拡張メソッド

Page 112: C#や.NET Frameworkがやっていること

ラムダ式

•匿名関数を簡単に書ける

• C++ のラムダ式と比べてシンプルなのは• 型推論がやりやすい• const& とかがない• ガベージ コレクションがあるし、変数キャプチャ

し放題

source.Select(x => x * x);( シーケンスの全要素を二乗 )

Page 113: C#や.NET Frameworkがやっていること

匿名型

• 1 か所でしか使わないような型は作らなくていい

• immutable なクラスを自動生成• GetHashCode 、等値比較、 ToString を完備

source.GroupBy(p => new { p.X, p.Y });(X と Y でグループ化 )

Page 114: C#や.NET Frameworkがやっていること

拡張メソッド

• 静的メソッドを後置き記法で書ける

• 単に語順を変えるだけ• 静的メソッドは pure function である限り無害

• pure = 状態を持たない同じ入力を与えたら常に同じ結果が帰ってくる

source.Select(x => x * x);

System.Linq.Enumerable.Select( source, x => x * x);

同じ意味

C++ でも関数オブジェクトに対する演算子オーバーロード似たことできなくはない

Page 115: C#や.NET Frameworkがやっていること

dynamicC# 4.0

動的コード生成ダック タイピング

Page 116: C#や.NET Frameworkがやっていること

dynamic 型

• C# 4.0 の dynamic 型• 結構用途が限られてて• できること

• 静的な型に対する動的コード生成• DLR 言語との連携

• できるけど過剰スペックなこと• JSON みたいなスキーマレスなデータ読み書き

• できないこと• メタプログラミング• C# のスクリプト的実行

Page 117: C#や.NET Frameworkがやっていること

主なターゲットは静的な型

• .NET の型情報に基づいた動的コード生成• IDynamicMetaObjectProvider を実装すれば型情報なくてもコード生成できるけども、あまりやる人いない

•利用例• 多重ディスパッチとか• ダック タイピングとか

Page 118: C#や.NET Frameworkがやっていること

多重ディスパッチ

• 静的な型に対してclass Base { }class A : Base { }class B : Base { }

Page 119: C#や.NET Frameworkがやっていること

多重ディスパッチ

• x, y の両方の型で動的に分岐• 仮想メソッドでは (素直には ) できない

static class Extensions{    public static string Dispatch(this Base x, Base y)    {        return (string)X((dynamic)x, (dynamic)y);    }    static string X(A x, A y) { return "A - A"; }    static string X(A x, B y) { return "A - B"; }    static string X(Base x, Base y) { return "others"; }}

動的な呼び出し

Page 120: C#や.NET Frameworkがやっていること

多重ディスパッチ

•呼び出し例

static void Main(){    Dispatch(new A(), new A()); // A - A    Dispatch(new A(), new B()); // A - B    Dispatch(new B(), new B()); // others    Dispatch(new B(), new A()); // others}

static void Dispatch(Base x, Base y){    Console.WriteLine(x.Dispatch(y));}

Page 121: C#や.NET Frameworkがやっていること

ダック タイピング

• 静的な型に対してclass Point{    public int X { get; set; }    public int Y { get; set; }    public override string ToString()    {        return "Point(" + X + ", " + Y + ")";    }}

Page 122: C#や.NET Frameworkがやっていること

ダック タイピング

• 同じ名前のメンバーを持つ別の型をまとめて処理

static class Extensions{    public static void CopyTo<T, U>(this T p, U q)    {        dynamic x = p;        dynamic y = q;        y.X = x.X;        y.Y = x.Y;    }}

X, Y を持つ任意の型に使える

Page 123: C#や.NET Frameworkがやっていること

ダック タイピング

•呼び出し例

var p = new Point();new { X = 10, Y = 20 }.CopyTo(p);Console.WriteLine(p);

Page 124: C#や.NET Frameworkがやっていること

DLR連携

• DLR (Dynamic Language Runtime)• 例 : IronPython

• DLR は内部的に .NET の型を生成してるので内部実装的にはこれも「静的な型に対する動的コード生成」

var py = IronPython.Hosting.Python.CreateEngine();dynamic p = py.Execute("['a', 'b', 1, 2]"); for (var i = 0; i < 4; i++)    Console.WriteLine(p[i]);

Page 125: C#や.NET Frameworkがやっていること

dynamic の内部実装

• コード生成結果

dynamic X(dynamic x){    return x.X;}

object X(object x){    if (_site1 == null)    {        _site1 = CallSite<Func<CallSite, object, object>>.Create(            Binder.GetMember(CSharpBinderFlags.None, "X", typeof(Program),            new CSharpArgumentInfo[]            {                CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null)            }));    }    return _site1.Target(_site1, x);}

実際にはobject

動的コード生成用の情報

これが本体

Page 126: C#や.NET Frameworkがやっていること

CallSite.Target の中身

• メソッドを動的コード生成してる• 生成したメソッドはキャッシュして持っておく

(inline method cache)

static object _anonymous(CallSite site, object x){    return site.Target を更新する処理}

_site1.Target の初期状態

Page 127: C#や.NET Frameworkがやっていること

CallSite.Target の中身

• メソッドを動的コード生成してる• 生成したメソッドはキャッシュして持っておく

(inline method cache)

• 同じ型に対して何度も呼ぶ分には高性能

static object _anonymous(CallSite site, object x){    if (x is Point) return ((Point)x).X;    else return site.Target を更新する処理}

メソッド X に Point 型のインスタンスを渡した後

1 行追加単なる型判定+キャス

Page 128: C#や.NET Frameworkがやっていること

CallSite.Target の中身

• メソッドを動的コード生成してる• 生成したメソッドはキャッシュして持っておく

(inline method cache)

static object _anonymous(CallSite site, object x){    if (x is Point) return ((Point)x).X;    if (x is Vector3D) return ((Vector3D)x).X;    else return site.Target を更新する処理}

さらに、メソッド X に Vector3d 型のインスタンスを渡した後

もう 1 行追加

ちなみに、最近はスクリプト言語でも、内部的に型を作って、 inline method

cacheで高速化してる

Page 129: C#や.NET Frameworkがやっていること

“heavy” dynamic

• 用途によっては高性能なものの…• 多くの場合、過剰スペック• できるけど過剰スペックな例

• JSON みたいなスキーマレスなデータ読み書き

• もっと” light-weight” な dynamic が必要( 今の C# にはない。要望としてはある )

x.X;x.M();

x.Get("X");x.Invoke("M");

例 : 規約ベースの置き換え

Page 130: C#や.NET Frameworkがやっていること

できないこと

• メンバー名がコンパイル時に既知でないとダメ

• なのでメタプログラミングには使えない• そのためにはもっと低レイヤーな API 使う

• IL Emit• Expression Tree• Roslyn

dynamic x = p;dynamic y = q;y.X = x.X;y.Y = x.Y;

コンパイル時に既知

Page 131: C#や.NET Frameworkがやっていること

コード生成 API

高級言語(C#)

構文木

IL

ネイティブ コード

parse

emit

JIT

Roslyn†

式ツリー

(System.Linq.Expressions)

ILGenerator

† 新しい C# コンパイラーのコードネーム動的なコンパイルも可能

Page 132: C#や.NET Frameworkがやっていること

コード生成にかかる時間

• あんまり動的にやりたくない

• 当たり前だけど parse は遅い• quoteほしい• できればコンパイル時生成でメタプログラミングしたい

かかった時間 [ ミリ秒 ]

ILGenerator(JIT)

39.89

Expressions(emit→JIT)

67.94

Roslyn(parse→emit→JIT)

4314

倍遅い

2桁遅い

ある環境で、あるコードの生成結果の例

Page 133: C#や.NET Frameworkがやっていること

async/awaitC# 5.0

非同期処理

Page 134: C#や.NET Frameworkがやっていること

非同期処理しんどい

•普通に非同期処理やったらコールバック地獄• begin/end地獄• イベント地獄• then地獄( ContinueWith地獄)

x.BeginX(args, state, ar =>{ var result = x.EndX(); …});

x.XCompleted += (s, arg) =>{ var result = arg.Result; …});x.XAsync();

x.XAsync() .ContinueWith(t => { var result = t.Result; … };非同期呼び出しが 1回だか

らこの程度で済んでる

Page 135: C#や.NET Frameworkがやっていること

•複数の確認ダイアログ表示

面倒な非同期処理の例

確認 1チェック

確認 2チェック

確認 3チェック

No

No

Yes

Yes

Yes

確認フロー

結果表示

No

ゲームでアイテムを合成します

レア アイテムですよ?

合成強化済みですよ?

もう強化限界ですよ?

ユーザーからの入力待ちも非同期処理

Page 136: C#や.NET Frameworkがやっていること

同期処理if (Check1.IsChecked){

var result = Dialog.ShowDialog(" 確認 1", "1 つ目の確認作業 ");if (!result) return false;

} if (Check2.IsChecked){

var result = Dialog.ShowDialog(" 確認 2", "2 つ目の確認作業 ");if (!result) return false;

} if (Check3.IsChecked){

var result = Dialog.ShowDialog(" 確認 3", "3 つ目の確認作業 ");if (!result) return false;

} return true;

Page 137: C#や.NET Frameworkがやっていること

非同期処理 (旧 )•画面に収まるように

フォント サイズ調整• 4pt• ほんの 84 行ほど

• ちなみに• 部分部分を関数化して多少は整理できる• ダイアログ 3 つだからま

だこの程度で済む

if (Check1.IsChecked){

Dialog.BeginShowDialog(" 確認 1", "1 つ目の確認作業 ", result =>{

if (!result){

onComplete(false);return;

if (.Check2.IsChecked){

Dialog.BeginShowDialog(" 確認 2", "2 つ目の確認作業 ", result2 =>{

if (!result2){

onComplete(false);return;

if (Check3.IsChecked){

Dialog.BeginShowDialog(" 確認 3", "3 つ目の確認作業 ", result3 =>{

onComplete(result3);});

}else

onComplete(true);});

}else if (Check3.IsChecked){

Dialog.BeginShowDialog(" 確認 3", "3 つ目の確認作業 ", result3 =>{

onComplete(result3);});

}else

onComplete(true);});

}else if (Check2.IsChecked){

Dialog.BeginShowDialog(" 確認 2", "2 つ目の確認作業 ", result =>{

if (!result){

onComplete(false);return;

if (Check3.IsChecked){

Dialog.BeginShowDialog(" 確認 3", "3 つ目の確認作業 ", result3 =>{

onComplete(result);});

}else

onComplete(true);});

}else if (Check3.IsChecked){

Dialog.BeginShowDialog(" 確認 3", "3 つ目の確認作業 ", result3 =>{

onComplete(result3);});

}else

onComplete(true); 

Page 138: C#や.NET Frameworkがやっていること

非同期処理( C# 5.0 )if (this.Check1.IsChecked ?? false){

var result = await Dialog.ShowDialogAsync(" 確認 1", "1 つ目の確認作業 ");if (!result) return false;

} if (this.Check2.IsChecked ?? false){

var result = await Dialog.ShowDialogAsync(" 確認 2", "2 つ目の確認作業 ");if (!result) return false;

} if (this.Check3.IsChecked ?? false){

var result = await Dialog.ShowDialogAsync(" 確認 3", "3 つ目の確認作業 ");if (!result) return false;

} return true; • 同期処理と比べて await演算子が増えた

だけ• ダイアログの数が増えても平気

Page 139: C#や.NET Frameworkがやっていること

イテレーターと似た仕組み

• イテレーター ( ジェネレーター ) があれば、割と単純なラッパーで非同期処理も可能• 要は、「中断と再開」

• 例えば、 TypeScript では• 現状でも await の MS社内実装は持ってる• いまだと、生成される JavaScript が悲惨すぎて大変• EcmaScript にジェネレーターが実装されてから、

TypeScript に await を追加する予定

Page 140: C#や.NET Frameworkがやっていること

参考 : C# のイテレーター(再 )• 中断と再開

class MethodEnumerator : IEnumerator<int>{    public int Current { get; private set; }    private int _state = 0;     public bool MoveNext()    {        switch (_state)        {            case 0:             Current = 1;            _state = 1;            return true;            case 1:             Current = 2;            _state = 2;            return true;            case 2:             default:            return false;        }    }}

IEnumerable<int> Method(){    yield return 1;

    yield return 2;

}

Current = 1;_state = 1;return true;case 1:

状態の記録

中断

再開用のラベル

Page 141: C#や.NET Frameworkがやっていること

基本的な考え方

•概念としては イテレーター +継続呼び出し

async Task<int> Method(){    var x = await task1;    var y = await task2;}

_state = 1;if (!task1.IsCompleted){    task1.ContinueWith(a);    return;}case 1:var x = task1.Result;

中断

状態の記録

結果の受け取り

再開用のラベル

非同期処理が終わったら続きから呼び出してもらう

Page 142: C#や.NET Frameworkがやっていること

実際の展開結果

• 実際はもう少し複雑• Awaiter というものを介する (Awaitableパター

ン )_state = 1;var awaiter1 = task1.GetAwaiter();if (!awaiter1.IsCompleted){    awaiter1.OnCompleted(a);    return;}case 1:var x = awaiter1.GetResult();

• Awaiter を自作することで、 await の挙動を変更可能• Task以外も await可能

Page 143: C#や.NET Frameworkがやっていること

C++ 1y

• C++ にも await が載るかも• C++ 17 に向けて標準化案出てる• C++/CX向けの実装を元に MS が提案

• 前述の通り、ついでにジェネレーターも

future<void> f(stream str) async{ shared_ptr<vector> buf = ...; int count = await str.read(512, buf); return count + 11;}

Page 144: C#や.NET Frameworkがやっていること

もう少し低レイヤーな話

• Task クラス

• スレッド機能だけあればいいわけじゃない• Task Pool 、同期コンテキストとか

async Task<int> Method(){    var x = await task1;    var y = await task2;}

_state = 1;if (!task1.IsCompleted){    task1.ContinueWith(a);    return;}case 1:var x = task1.Result;

Page 145: C#や.NET Frameworkがやっていること

スレッド

•非同期処理の最も低レイヤーな部分• ただし、高負荷

• 細々と大量の処理 ( タスク ) をこなすには向かない• 切り替え(コンテキスト スイッチ)のコストが高すぎる

for (int i = 0; i < 1000; i++){ var t = new Thread(Worker); t.Start();}

1000個の処理を同時実行

Page 146: C#や.NET Frameworkがやっていること

2種類のマルチタスク

※cooperative

プリエンプティブ

• ハードウェア タイマーを使って強制割り込み• OS が特権的にスレッド切り替えを行う• 利点 : 公平(どんなタスクも等しく OS に制御奪われる)• 欠点 : 高負荷(切り替えコストと使用リソース量)

協調的※

• 各タスクが責任を持って終了する• 1 つのタスクが終わるまで次のタスクは始まらない• 利点 : 低負荷• 欠点 : 不公平( 1 タスクの裏切りが、全体をフリーズさせる)

Page 147: C#や.NET Frameworkがやっていること

スレッド プール

• スレッドを可能な限り使いまわす仕組み• プリエンプティブなスレッド数本の上に• 協調的なタスク キューを用意

スレッド プール

キュータスク

1タスク

2

数本のスレッドだけ用意

空いているスレッドを探して実行(長時間空かない時だけ新規スレッド作

成)

新規タスク

タスクは一度キューに溜め

ちなみに、 std::future は Thread Pool な実装になってるはず

Page 148: C#や.NET Frameworkがやっていること

スレッド プールの向上

• Work Stealing Queue• lock-free 実装なローカル キュー

• できる限りスレッド切り替えが起きないように

ローカルキュー 1

ローカルキュー 2

スレッド 1 スレッド 2

グローバルキュー

①スレッドごとにキューを持つ ②

ローカル キューが空のとき、他のスレッドからタスクを奪取

Page 149: C#や.NET Frameworkがやっていること

シングル スレッド必須なもの

• スレッド安全なコードは高コスト

• いっそ、単一スレッド動作を前提に• 典型例は GUI

• C#/.NET に限らずたいていの GUI フレームワークはシングル スレッド動作

• 低レイヤー API ( DirectX とか OpenGL とか)も、 1つのスレッドからしか扱えない

Page 150: C#や.NET Frameworkがやっていること

典型例 : UI スレッド

• GUI は単一スレッド動作( UI スレッド)• ユーザーからの入力受け付け• 画面の更新

UI スレッドユーザー

からの入力

OK

グラフィック

更新

他のスレッド

処理中は応答不可

他のスレッドからは更新不可

Page 151: C#や.NET Frameworkがやっていること

矛盾

単一スレッドからしかUI更新でき

ないそのスレッドを止めると UI フリー

シングル スレッド推奨

マルチ スレッド推奨

OK

Page 152: C#や.NET Frameworkがやっていること

解決策

1. スレッド プールで重たい処理2. UI スレッドに処理を戻してから UI更新

UI スレッド

OK

更新

他のスレッド

重たい処理

Dispatcher.Invoke

Task.Run

※ dispatcher: 配送者

渡す役割を担うのがディスパッチャー※

他の GUI フレームワークだと event queue とか handler とかいう名前で提供されたりするものの、やってることは一緒

いったん UI スレッドにメッセージを渡

Page 153: C#や.NET Frameworkがやっていること

おまけ : immutable

• 並列処理といえば immutable だけども• 書き換えが起こらないなら複数のスレッドで共有

しても安全

• この用途だと、 C++ の const は不十分• あれは参照渡しを安全に行う用であって• 呼び出し元の側では const とは限らず、書き換わ

る可能性あり• あと、 mutable修飾子を付ければ const なオブ

ジェクトすら書き換えれる

Page 154: C#や.NET Frameworkがやっていること

Compiler as a ServiceFuture of C#, C# 6.0

コンパイラーの内部データを活用

Page 155: C#や.NET Frameworkがやっていること

Compiler as a Service

• 今、 C# はコンパイラーをフルスクラッチで作り直してる• Code name “Roslyn”• C# 実装の C# コンパイラー• コンパイラーの内部データを自由に使える

Page 156: C#や.NET Frameworkがやっていること

用途

• C# スクリプティング• C# で設定ファイル書きたい (むしろ XML がい

や )• アプリの再起動なしでロジック更新

• ウェブ サービスで使う• (GitHub とか想像してもらって ) コード リポジト

リのインデックス化• オンライン IDE 、ビルド、テスト

• IDE連携

Page 157: C#や.NET Frameworkがやっていること

構文ハイライト

• C# は文脈キーワードだらけstatic IEnumerable<async> async(){

var var = "var";var yield = new async();yield return yield;Func<string, Task<int>> async = async x =>{

await Task.Delay(100);return int.Parse(x);

};var await = async(var).GetAwaiter().GetResult();

}

メソッド名

クラス名

変数 キーワード

Page 158: C#や.NET Frameworkがやっていること

リアルタイム エラー検出

•エラー検出タイミングがビルド時とか遅い• Visual Studio は常時やってる

Page 159: C#や.NET Frameworkがやっていること

コード補完

• タイピングめんどくさい•文法やライブラリ、いちいち覚えたくない

Page 160: C#や.NET Frameworkがやっていること

リファクタリング

•最初からきれいなコード書くのめんどくさい• でもほっときたくない

Page 161: C#や.NET Frameworkがやっていること

ということで

• 今、コンパイラーに求められる要件• ソースコードのどこからどこまで(何行何列目)

が何かという情報がとれる• 文脈に応じてソースコードを生成したり、書き替

えたりできる• リアルタイム処理を必要とするので、パフォーマ

ンスも求められる

Page 162: C#や.NET Frameworkがやっていること

実はこれまで

• コンパイラー (パーサー ) を 2重開発してた• コンパイル用• IDE 用• まして、サード パーティ製コード解析プラグイン

も含めると、 3重開発

• Java (Eclipse とか IntelliJ とか ) でも• 「 Eclipse が対応するまで Java 8 プレビュー試せ

ないや」

Page 163: C#や.NET Frameworkがやっていること

Clang

• Clang はその辺りをゴールの 1 つに掲げてる• IDE で使う前提• リファクタリングに使いやすいデータ構造の構文木• インクリメンタル コンパイルとかもしやすい

Page 164: C#や.NET Frameworkがやっていること

IDE連携まで考えると

•簡単そうに見える文法ですら、実装コストかなり高い• 2重開発はそれだけ大変• だから IDE を信用しない / できない人も多い

• IDE対応待ってられない /待ちたくない

• “Roslyn” はそれを解消• C# に新機能を足しやすくなる• C# 6.0

Page 165: C#や.NET Frameworkがやっていること

C# 6.0 (予定 ) の例

public class Point(int x, int y){

public int X => x;public int Y => y;

}

Primary Constructor / Property Expressions

while ((var line = stream.ReadLine()) != null)

line ...

if ((var x = obj as Point) != null)x ...

Declaration Expressions

immutable な型を作りやすく

「式」で書けることの幅が広がる

Page 166: C#や.NET Frameworkがやっていること

以上。

Page 167: C#や.NET Frameworkがやっていること

まとめ

• .NET Framework の基礎機能• Garbage Collection• AppDomain• メタデータ、 JIT• PCL

• C# 2.0 ~ 5.0… 6.0!• ジェネリック、イテレーター• LINQ 、 dynamic 、 async/await• Compiler as a Service