Upload
-
View
24.945
Download
0
Embed Size (px)
DESCRIPTION
2014/6/28 CLR/H in Tokyo 第3回 にて登壇
Citation preview
C# や .NET Frameworkがやっていること
第二版
岩永 信之
++C++; // 未確認飛行 C
今日の内容
• C# や .NET Framework がやっていること• どうしてそういう機能がある• ( 自前で実装するなら ) どうやればできる• ( 自前でやる上で ) 気を付ける必要がある点
注意
• ベースは 3 月に Boost 勉強会でやった内容• 通常の 3 倍速で 1 時間で話した
• 本来、 1 時間で話す内容じゃない• かなりの前提知識を仮定
• 3 時間話します• いくつか追記• 前提をだいぶ緩めたので背景説明など• .NET vNext がらみ追加• 5 月の非同期勉強会での内容も少しマージ
同じような内容でも回数重ねてこなれてきてたり
目次• 1 時間目• メモリ管理• マルチ タスク管理
• 2 時間目• メタデータ
• 3 時間目• C# 言語機能
1
2
3
1 時間目• メモリ管理• ヒープ自動管理• 境界チェック• ドメイン分離
• マルチ タスク管理• スレッドと非同期処理
1
2
3
“Managed”
• .NET Framework といえば Managed• .NET がいろいろ管理
• メモリ リーク防止• セキュリティ ホール防止
• v1 リリース後、一番進歩が激しいのは非同期• 効率的なタスク管理• I/O-bound な非同期処理
Garbage Collectionメモリ リークをなくすために
メモリ管理 (1)
• スタック• 一番上に積む・一番上から取り出す
• ローカル変数用• 管理は楽• スコープがはっきりしてないと使えない
int x = 1;int y = 2;int z = 4;int sum = x + y + z;
1 1
2
load 1 load 2
3
add
3
4
load 4
7
add
メモリ管理 (2)
• ヒープ• 連結リスト構造で任意の位置にメモリ確保
• 動的なメモリ利用できる• 管理が大変
• 消し忘れ ( メモリ リーク ) があり得る
使用中かどうかのフラグ
☑□ □ ☑
次のヒープの位置
前のヒープの位置
… …
値型と参照型
• 値型• int とか byte とか• struct とか enum と
かスタック ヒープ
他の型
他の型
スタック上や、他の型の中に直接埋め込まれる
• 参照型• string とか• class とか delegate
とかスタック ヒープ
スタック上や他の型の中には参照情報だけある
ヒープ上に置かれる
ヒープ管理
• 怠ると• 徐々にアプリが重くなる• 長時間稼働させると突然の死
• その割に大変• ( 自動管理がない頃 )
プログラミングの苦労の半分くらい占める• 本当にやりたいことに注力できてない
• 自動化したい不要なオブジェクトの回収 = ゴミ を集め (garbage
collection)
代表的な手法
• 参照カウント• 生成・コピー代入のたびにカウントを +1• 変数がスコープ外れるたびに - 1• カウントが 0 になったらメモリ削除
• Mark & Sweep• ルート†からたどれるオブジェクトに印を付ける
• 印のついてないオブジェクトを削除
† スタックや静的フィールド中のオブジェクトなど参照の起点となる場所
mark
sweep
Mark & Sweep
• ルートから参照をたどる
Mark
ヒープルート
Sweep
使用中オブジェクト
未使用オブジェクト
Compaction
• GC 時にオブジェクトの位置を移動
使用中オブジェクト
未使用オブジェクト
隙間をなくすように移動
後ろが丸ごと空くので次のメモリ確保が楽
Generation
• 移動したオブジェクトはしばらくノータッチ
Gen1 Gen0
Gen0
しばらくここはノータッチ
この範囲でだけメモリ確保・
GC
オブジェクトの寿命統計的に• 短いものはとことん短く• 長いものはとことん長い
一度 GC かかったオブジェクトはしばらく放置
.NET Framework の GC
• .NET Framework の GC は Mark & Sweep• Compaction あり• 世代別 (3世代、 Gen0~ 2)• Background スレッドで並列実行
Mark & Sweep と参照カウント•比較• 一長一短ある
Mark & Sweep 参照カウント
メモリ確保 ○ 末尾を見るだけ × 空いている場所を探す× カウント用の領域が追
加で必要
変数のコピー ○ ポインターのコピー × ポインターのコピーに加え、参照数のカウント アップ
メモリ解放 × Mark や Compactionに時間がかかる
× 負担が 1 か所に集中
○ カウントが 0 になった時に delete するだけ
× 循環参照が苦手
Throughput
• トータルの性能 (throughput) は Mark & Sweep の方がいい
Mark & Sweep 参照カウント
メモリ確保 ○ 末尾を見るだけ × 空いている場所を探す× カウント用の領域が追
加で必要
変数のコピー ○ ポインターのコピー × ポインターのコピーに加え、参照数のカウント アップ
メモリ解放 × Mark や Compactionに時間がかかる
× 負担が 1 か所に集中
○ カウントが 0 になった時に delete するだけ
× 循環参照が苦手まとめてやる方がバラバラにやるよりスループットは
いい
頻度が高い操作なのでここの負担が大きいと全体の性能落ちる
特に、スレッド安全を求めるときついたかがインクリメントでも、 atomic性保証するとそこそこの負担
参照局所性も高くなってキャッシュが効きやすい
短所の軽減
•短所も、まったく打つ手がないわけじゃない
Mark & Sweep 参照カウント
メモリ確保 ○ 末尾を見るだけ × 空いている場所を探す× カウント用の領域が追
加で必要
変数のコピー ○ ポインターのコピー × ポインターのコピーに加え、参照数のカウント アップ
メモリ解放 × Mark や Compactionに時間がかかる
× 負担が 1 か所に集中
○ カウントが 0 になった時に delete するだけ
× 循環参照が苦手
.NET 4以降、 Background スレッ
ド実行してる C++ 11 的には「move semantics活用してね」
自前メモリ管理との混在
• スコープが短いならスタックにとればいいのに• C# は class は必ずヒープ、 struct はスタックに
なる• 使い分けれない
• Mark & Sweep 管理領域内のポインターを管理外に渡すときには注意が必要• Compaction でメモリ移動しないように「ピン止め」が必要• 結構ガベージ コレクションのパフォーマンス落とす
おまけ : 他の言語
• C++: 主に参照カウント方式のライブラリ利用• 型がはっきりしない言語では Mark & Sweep しにくい
• できなくはないけども「保守的」になる ( 効率落とす )• 数値を無差別にポインター扱いして Mark する
• Python とか : 参照カウントと Mark & Sweep の併用
• ○ 循環参照問題避けつつ、負担を 1 か所に集中させない• × 性能的には悪いとこどり
• Go: 割当先を自動判別• スコープ内で完結してたらスタックに、さもなくばヒープに• これはこれでスタックの浪費激しそうなんだけども…
• なので、可変長スタック持ってる
注意 : GC あってもリーク
• 長時間生きているオブジェクト†が参照持ったままになると、 GC対象になれない
† ワーストケースはアプリ稼働中ずっと生きているオブジェクト
class View : UserControl{ public View(Model model) { model.PropertyChanged += (sender, e) => { // データの表示を更新 }; }}
よく起こり得る例 ビューは画面遷移で消える
モデルはずっと生きてる
イベント購読で、モデルがビューの参照持
つ解除 (-=) しないとメモリ リーク
ポイント
• メモリ管理は大変なので自動化• Mark & Sweep• いろいろ賢いアルゴリズム搭載してるし、ほとん
どの場合、任せた方がいい
• GC あってもメモリ リークは起こり得るので注意
境界チェック意図しないメモリ領域にはアクセスさせない
配列の境界チェック
• .NET の配列は厳しい• 常に境界チェックしてる
• 要は buffer overrun† 防止• ちなみに JIT最適化で不要な境界チェック消してる
( 明らかに境界を侵さないものはチェック不要 )• for (var i; i < a.Length; ++i) とか• foreach (var item in a) とか
配列 a
OutOfRange
OutOfRange
a[-1] a[length+1]
† クラッシュやセキュリティ ホールの温床
unsafe
• とはいえ、 C# にもポインターあるんだけども
var x = new[] { 1, 2, 3, 4, 5 };unsafe{ fixed(int* px = &x[0]) { Console.WriteLine(px[100]); }}
メモリ移動の防止(ピン止め)
範囲チェックなし
安全でないコードこの中でだけポインター利用可能
buffer overrun やりたい放題
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
ピン止め必須 • 値型のメンバーのアドレスは取れる
• オブジェクト自体のアドレスは取れない
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
メンバーが全部値型
無制限にアドレス取れる
† 正確には、再帰的に全子要素が値型、かつ、型パラメーターも値型
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
つ
本体のアドレス取れない値型のメンバーのところだけはアドレス取れる
ポイント
• unsafe• 危険なことは基本認めない
• 面倒な追加の構文を要求• コンパイル オプションでも /unsafe の明記必須
• fixed• ガベージ コレクションとの兼ね合い
• Compaction阻害になるので注意
•「 Unmanaged 型」に限る• 実装依存な部分 (vtable とか)や、
型の定義側の変更が利用側に極力影響しないように
AppDomain実行環境の分離セキュリティ保証
コンポーネントの連携
• 他のアプリの機能を自分のアプリから呼びたい• Word とか Excel とかのドキュメントを出力
•サーバー上に自分のアプリを配置したい• IIS 上に( ASP.NET)• SQL Server 上に
• ( 必ずしも ) 信用できない• セキュリティ保証• 参照先のクラッシュにアプリ /サーバーが巻き込まれないように
• コンポーネントのバージョン アップ
AppDomain
•単一プロセス内で、分離された複数の実行領域 (domain) を提供
•「分離」• plugin: 動的なロード / アンロード• security: AppDomain ごとに異なる権限付与• isolation: 別メモリ空間
AppDomain 1
AppDomain 2
…
プロセス
必ずしも信用できないコードを安全に、動的に呼び出し
ドメイン間には壁がある
•別メモリ空間に分離されてる
AppDomain 1 AppDomain 2
互いに独立
ドメイン間の通信
• マーシャリング (marshaling)• marshal (司令官、案内係 ) の命令通りにしか壁を超えれない
AppDomain 1 AppDomain 2
• 司令がダメといったら通れない• 司令の指示通りの形式にいったんシリアライズしないといけない
AppDomain 1 AppDomain 2
ドメイン間の通信
•ダメな例
これを B に渡したいとして
あるオブジェクト
AppDomain 1 AppDomain 2
ドメイン間の通信
•ダメな例
shallow copy なんてしようもんなら
A側のメモリへの参照が残る
AppDomain 1 AppDomain 2
ドメイン間の通信
• よい例
serializedeserializ
e
{ {1, 2}, {3, 4}, {5, 6}}
この辺りがマーシャリング
一度シリアライズ 必ず deep copy
.NET のマーシャリング
• 2種類• marshal by ref
• 参照を渡すんだけど、メモリ領域は直接触れない• メソッド越しにしか操作しちゃいけない• 「このメソッドを呼んでくれ」っていうメッセージだけ
がドメインを超えてわたって、実際の実行は相手側ドメインで ( プロキシ実行 )
• marshal by value• 値をシリアライズして相手側に渡す• 規定では、 BinarySeralizer を使用
private フィールドまで含めてリフレクションで内部状態を取得してシリアライズ
.NET のマーシャリング (文字列 )•文字列は特殊扱い
(文字コード変換が不要な場合 )• marshal by value みたいにシリアライズを経な
い• marshal by reference みたいにプロキシ実行し
ない• 参照を渡して、メモリを直接読んでもらう
• immutable かつ range-check 付きに作ってある• 変更不可なので参照を渡しても安全
• COM (ネイティブ ) に対して文字列を渡す時すらこの方式
AppDomain 1 AppDomain 2
その他のコスト
• マーシャリング以外にもいくらかコストが• 例えばライブラリの読み込み
Library X Library X
別ドメインに全く同じライブラリを読み込んでも、それぞれ別イメージが作られる
Global Assembly Cache (GAC) にある DLL だけは別GAC 中のは同じメモリ イメージが共有される
おまけ : 他の言語
• COM なんかはマーシャリングやってる• というか「 COM の頃から」やってる• .NET の前身なので
• さもなくば、プロセス分離してプロセス間通信• OS特権がないと他プロセスのメモリにはアクセ
スできない• 安全だけどかなり高負荷
• RPC† とか一時期流行ったけども廃れた
† ただの関数呼び出しに見えるコードで、内部的にプロセス間通信する知らないうちに性能落とすとか、リモート側でのエラーが処理しにくいとか
ポイント
• プラグインとかやるなら「分離」が必須• とはいえ、プロセス分けると負担が大きすぎる
• プロセス内で分離を保証する仕組み• AppDomain• マーシャリング
• それなりにコストがかかるけども、セキュリティには変えられない
スレッド応答性のよいプログラムを書くためにOS やアプリ全体をフリーズさせないために
マルチタスク
• コンピューター内で複数のタスクが同時に動作• CPU コア数に制限されないタスク 1 タスク 2 タスク 3 …
タスクの動作期間
実際に CPU を使って動いている期間
1 つの CPU コアを複数のタスクがシェアして
る問題は• どうやって他のタスクに CPU を譲るか• 誰がどうスケジューリングするか
2種類のマルチタスク
† preemptive: 専買権を持つ、横取りする※cooperative
プリエンプティブ†
• ハードウェア タイマーを使って強制割り込み• OS が特権的にスレッド切り替えを行う• ○利点 : 公平 ( どんなタスクも等しく OS に制御奪われる )• ×欠点 : 高負荷 (切り替えコストと使用リソース量が多い )
協調的※
• 各タスクが責任を持って終了する• 1 つのタスクが終わるまで次のタスクは始まらない• ○利点 : 低負荷• ×欠点 : 不公平 (1 タスクの裏切りが、全体をフリーズさせる )
なのでスレッドはこっち
これが致命的
ただ、問題はこれ
問題 : スレッドは高コスト†
•細々としたタスクを大量にこなすには向かない
for (int i = 0; i < 1000; i++){ var t = new Thread(Worker); t.Start();}
大量の処理をスレッド実行
リソース消費大切り替え頻発
…
† スレッドごとに数 MB のメモリを確保したりスレッド切り替えに OS特権が必要だったり文脈が切り替わっちゃうのでキャッシュ ミスしたり
解決策 : スレッド プール
• スレッドを可能な限り使いまわす仕組み• プリエンプティブなスレッド数本の上に• 協調的なタスク キューを用意
スレッド プール
キュータスク
1タスク
2
…
数本※のスレッドだけ用意
空いているスレッドを探して実行(長時間空かない時だけ新規スレッド作
成)
新規タスク
タスクは一度キューに溜め
る
※ 理想的には CPU のコア数分だけ
スレッド プールの性能向上
• Work Stealing Queue• lock-free 実装†なローカル キュー• できる限りスレッド切り替えが起きない作り
ローカルキュー 1
ローカルキュー 2
スレッド 1 スレッド 2
グローバルキュー
①スレッドごとにキューを持つまず自分用のキューからタスク実行
②ローカル キューが空のとき、他のスレッドからタスクを奪取
† 今回は詳細割愛
スレッド プールの性能向上
• Work Stealing Queue• lock-free 実装†なローカル キュー• できる限りスレッド切り替えが起きない作り
ローカルキュー 1
ローカルキュー 2
スレッド 1 スレッド 2
グローバルキュー
①スレッドごとにキューを持つまず自分用のキューからタスク実行
②ローカル キューが空のとき、他のスレッドからタスクを奪取
ポイント• スレッドは高コスト• Thread クラスはこっち
• スレッド プールの利用推奨• Task クラスはこっち
† 今回は詳細割愛
問題 : CPU の外の世界は遅い
• 実行速度が全然違う
ALU
メイン・メモリ
レジ
スタ
ーCPU 周辺機器
数千~下手すると数万、数億倍遅い
>>>
2種類の負荷
• CPU-bound (CPU が性能を縛る )• マルチコア CPU の性能を最大限引き出したい• UI スレッドを止めたくない
• I/O-bound (I/O※ が性能を縛る )• ハードウェア割り込み待つだけ• CPU は使わない• スレッドも必要ない
※ Input/Output: 外部ハードウェアとのやり取り ( 入出力 )
I/O完了待ち
• I/O-bound な処理にスレッドは不要
あるスレッド
要求
応答
この間何もしないのにスレッドを確保し続けるのはもったいない
解決策 : I/O完了ポート※
• スレッドを確保せず I/O を待つ仕組み• コールバックを登録して、割り込みを待つ• コールバック処理はスレッド プールで
スレッド プール
タスク1
タスク2…
※ I/O completion port
あるスレッドアプリ
I/O完了ポート
ハードウェア
I/O開始 I/O完了
コールバック登録
コールバック登録後、すぐにスレッド上での
処理を終了
割り込み信号
I/O完了ポート※
• スレッドを確保せず I/O を待つ仕組み• コールバックを登録して、割り込みを待つ• コールバック処理はスレッド プールで
スレッド プール
タスク1
タスク2…
※ I/O completion port
あるスレッドアプリ
I/O完了ポート
ハードウェア
I/O開始 I/O完了
コールバック登録
コールバック登録後、すぐにスレッド上での
処理を終了
割り込み信号
ポイント• I/O-bound な処理にスレッドを使っちゃダ
メ• I/O 用の非同期メソッドが用意されてる
( 内部的に I/O完了ポートを利用 )
問題 : スレッド安全保証
• スレッド安全なコードは高コスト
• いっそ、単一スレッド動作を前提に• メッセージ ポンプ
MSG msg;while (PeekMessage(&msg, (HWND)NULL, 0, 0, PM_REMOVE)){ TranslateMessage(&msg); DispatchMessage(&msg);}
こんな感じのループが単一スレッドで動作
他のスレッドからメッセージをキュー越しに受け取って処理
典型例 : UI スレッド
• GUI はシングル スレッド動作 (UI スレッド )†• ユーザーからの入力受け付け• 画面の更新
UI スレッドユーザー
からの入力
OK
グラフィック
更新
他のスレッド
処理中は応答不可
他のスレッドからは更新不可
† C#/.NET に限らず、だいたいの言語・環境で GUI はシングル スレッド動作
矛盾
単一スレッドからしかUI更新でき
ないそのスレッドを止めると UI フリー
ズ
シングル スレッド推奨
マルチ スレッド推奨
OK
解決策 : メッセージ配送
1. スレッド プールで重たい処理2. UI スレッドに処理を戻してから UI更新
UI スレッド
OK
更新
他のスレッド
重たい処理
Dispatcher.Invoke
Task.Run
※ dispatcher: 配送者
渡す役割を担うのがディスパッチャー※
他の GUI フレームワークだと event queue とか handler とかいう名前で提供されたりするものの、やってることは一緒
いったん UI スレッドにメッセージを渡
す
おまけ : 他の言語
• スクリプト言語の類はだいたいスレッド機能持ってない• GUI を前提にしていないか• GUI がフリーズするほどの処理を書くことを前提
にしていないか
• C++ とかは最近 (11 で ) ようやく標準化• それまでは、スレッドは結構環境依存
ポイント
•応答性と全体のパフォーマンスとの兼ね合い• スレッド プール
• I/O待ちのためにスレッドは使っちゃダメ• I/O完了ポート
• スレッド安全保証はそれなりに高コスト• シングル スレッドでメッセージ ポンプ• メッセージ配送
Taskクラス
2 時間目• メタデータ• 動的リンク• JIT• PCL
1
2
3
メタデータとは
• 実行可能ファイル中には本来不要なデータ
• プログラムを作るためのデータ• 実行に必要なデータよりもメタ (高次 )
var x = p.X;var y = p.Y;var z = p.Z;
var pp = (byte*)&p;var x = *((int*)pp);var y = *((int*)(pp + 4));var z = *((int*)(pp + 8));
プロパティ名とか 単にプログラムを動かすだけな
ら相対アドレスだけわかればいい
.NET では、こういうメタデータを実行可能ファイルに残す
.NET のメタデータ
• .NET のメタデータ (≒ 型情報 )• DLL にどういう型が含まれるか• どういう型がどういうメンバーを持っているか• 外部のどういう DLL を参照しているか• バージョン情報
• DLL にメタデータを残すことで• プログラミング言語をまたげる• 動的リンクでのバージョン管理ができる
おまけ : 昔と今
• COM の頃• メタデータを自分で書いてた
( 自動化するツールあるけど )• .tlb/.olb ファイル
• .NET Framework• .NET 自体がメタデータの規格を持ってる• C# とかをコンパイルするだけで作られる
おまけ : C++/CX
• C++ Component Extensions• マイクロソフトの C++拡張
C++/CX
素の標準C++ コード
COM コード
メタデータ(winmd)
コンパイル
C++ 内で完結して使う分にはオーバーヘッドな
し
COM を呼べる言語なら何からでも呼べる
.NET のメタデータと互換
.NET から簡単に呼べる
動的リンク実行可能ファイルにメタデータが含まれていることで
ライブラリ共有
• 前提 : ライブラリは共有する
アプリ A
アプリ B
ライブラリ X
ライブラリ Y
version 1
version 1
動的リンク
• ライブラリ単体での差し替え• セキュリティ ホールや、致命的なバグの修正
アプリ A
アプリ B
ライブラリ X
ライブラリ Y
version 2
version 1
更新不要差し替え
アプリ実行時にライブラリをリンクしなおしてる = 動的リンク
いまどきの事情
• ライブラリはアプリのパッケージに同梱• 動的なリンクいるの?
アプリ A ライブラリ X
ライブラリ Y アプリ B
ライブラリ X
ライブラリ Y
アプリ Aパッケージ アプリ Bパッケージ
別バイナリ・別々に配布別バージョンでも困らない
とはいえ…
差分ダウンロード※ 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インストール機
バージョンアップ時
更新ここだけ新しい
アプリ B
ライブラリ X
ライブラリ Y
アプリ間でライブラリ共有※ Windows ストア アプリはこういう仕組み持って
アプリ A ライブラリ X
ライブラリ Y
アプリ Aパッケージ
ライブラリ Y
アプリ Bパッケージ
差分
アプリ Aインストール機アプリ B
アプリ B インストール時
X 、 Y は同じものを共有(ハード リンク作るだけ)
version 1
version 1 version 1
version 1同じバージョン
パッケージ管理※ ASP.NET vNext はこういう仕組み持ってる
開発機 アプリ サーバー パッケージ リポジトリ
例http://nuget.orghttp://myget.org
project.json
*.cs
"dependencies": { "Library.A": "", "Library.B": ""}
var c = new Configuration();c.AddJsonFile("config.json");a.UseServices(services => …);
ソースコードだけをアップロード
サーバー上で編集可能
ライブラリの不足・更新はクラウドから取得
ポイント
• 動的にリンク• 部分更新・差分ダウンロード• ライブラリ共有• パッケージ管理
• リンクに必要な情報 ( メタデータ ) を実行可能ファイルに残す• メタデータも規格化されてる• C#/.NET の場合はコンパイルするだけで作られる
JIT(Just-in-Time compile)実際の動的リンクの挙動
中間コードと JIT コンパイル
• .NET Framework の中間言語
• 高級言語のコンパイラーを作る人と、 CPU ごとの最適化する人の分業化• セキュリティ チェックしやすくなったり• 動的リンク時に、コード修正の影響を JIT で吸収
高級言語(C# など )
中間言語(IL)
ネイティブコード
ビルド時にコンパイル
Just-in-Timeコンパイル
JIT である必要ないLLVM とかでも中間言語介してコンパイルして
る
ストア審査ではじくとか他にも手段はある
例
• (C# で ) こんな型があったとして
• 整数のフィールドを 3 つ持つ
public struct Point{ public int X; public int Y; public int Z;}
例
• こんなメソッドを書いたとする
• フィールドの掛け算
static int GetVolume(Point p){ return p.X * p.Y * p.Z;}
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}
型とかフィールドの名前がそのまま
残ってる
型情報メタデータ
ネイティブ コード
• 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 とかの数値に
型情報は残らない
メモリ レイアウト
• この 4 とか 8 の意味
public struct Point{
public int X;public int Y;public int Z;
}
Point
X
Y
Z
4バイト
8バイト
※レイアウトがどうなるかは環境依存
メモリ レイアウト
• この 4 とか 8 の意味
public struct Point{ public int X; public int Y; public int Z;}
Point
X
Y
Z
4バイト
8バイト
※レイアウトがどうなるかは環境依存
IL の時点までは名前で参照してる
ネイティブ コードはレイアウトを見て数値で参照してる
数値でのフィールド参照
• 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)
変更してみる
• 大して影響しなさそうなほんの些細な変更をしてみる
public struct Point{ public int X; public int Y; public int Z;}
public struct Point{ public int X; public int Z; public int Y;}
フィールドの順序変更
その結果起きること
• メモリ レイアウトが変わる※
Point
X
Y
Z
Point
X
Z
Y
※ この例(フィールド変更)以外でも、仮想メソッド テーブルとかいろいろレイアウトが変わるものがある
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 が吸収してくれる
ネイティブ レベルでの影響
• ここで影響が出る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更新が必要
利用側の再コンパイルが必要
ライブラリ側だけの差し替えじゃダメ
ただし…
• この役割に焦点を当てるなら…• 毎回毎回 JIT する必要ない• 全部が全部 IL な必要ない
Ngen
• Ngen.exe• Native Image Generator• IL を事前にネイティブ化するためのツール• 自前管理が必要
• アプリのインストーラー※とかを作って明示的に呼び出し
• 参照しているライブラリが更新された時には呼びなおす必要あり •かなり面倒なのでアプリを
Ngen することはめったにない• .NET 自体が標準ライブラリ
の高速化のために使ってる※ 要するに、 JIT の負担を起動時じゃなくてインストール時に前倒しする
Auto-Ngen
• .NET Framework 4.5以降なら• Ngen が Windowsサービスとして常に動いてる
• アイドル時に動作• 利用頻度の高いものを自動的に Ngen
• デスクトップ アプリの場合は GAC アセンブリのみ• Windows ストア アプリの場合はすべてのアセンブリ
•よく使うアプリの起動はだいぶ早くなる•インストール直後の起動は相変わらず遅
い
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
参照しているライブラリのレイアウトが変わった時に再コンパイ
ルが必要
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
ほぼネイティブ レイアウトのとこ
ろだけ抽象的に型情報を残しておく
MDIL: Machine Dependent Intermediate Language
MDIL (Compile in the Cloud)• ストア サーバー上で MDIL 化までやっておく
C#コード
IL
MDIL
ネイティブ コード
C# コンパイラー
MDIL コンパイラー
リンカー
開発環境で IL にコンパイル
Windows ストア サーバー上で IL を MDIL 化
Windows Phone 実機上ではレイアウトの解決 ( リンク ) だけ行う
インストール直後の起動も高速
※ Windows Phone アプリはこういう仕組み持ってる
ポイント
• JIT の主な利点• 動的リンクしやすい• ( 後述の、動的コード生成しやすいとかもある )
• ただし、常に Just-In-Time な必要ない• インストール時 → Ngen• サービスで定期的に → Auto-Ngen• ストア サーバー上で → Compile in the Cloud
vNext は JIT だけじゃない
• (詳細は後述 )選べる実行形態• 事前に完全ネイティブ化 → .NET Native• ソースコード配置 → Cloud Mode
リフレクション動的コード生成
メタデータ利用
• メタデータ ( プログラム生成に必要な情報 ) を持っているということは• 動的にプログラム コードを生成できる• 自己反映的動作 ( リフレクション )
コード生成 API
高級言語(C#)
構文木
IL
ネイティブ コード
parse
emit
JIT
.NET Compiler Service†
式ツリー
(System.Linq.Expressions)
ILGenerator
† 旧称 ( コードネーム )Roslyn動的なコンパイルが可能
C# コードから
• .NET Compiler Service の例
session.Execute<Func<Point, int>>("p => p.X * p.Y * p.Z")
C#ソースコードをコンパイル
C#→[parse]→ 構文木→ [emit]→IL→[JIT]→Native
構文木から
• System.Linq.Expressions
Expression.Lambda<Func<Point, int>>(Expression.Multiply(
Expression.Multiply(Expression.Field(p, x),Expression.Field(p, y)),
Expression.Field(p, z)),
IL を直接生成
• ILGenerator
var gen = m.GetILGenerator();gen.Emit(OpCodes.Ldarg_0);gen.Emit(OpCodes.Ldfld, x);gen.Emit(OpCodes.Ldarg_0);gen.Emit(OpCodes.Ldfld, y);gen.Emit(OpCodes.Mul);gen.Emit(OpCodes.Ldarg_0);gen.Emit(OpCodes.Ldfld, z);gen.Emit(OpCodes.Mul);gen.Emit(OpCodes.Ret);
コード生成にかかる時間
• 本当はあんまり動的にやりたくない
• 当たり前だけど parse は遅い• なので、 .NET はソースコード配布じゃなくて IL配布• できる限りコンパイル時生成したい
かかった時間 [ ミリ秒 ]
ILGenerator(JIT)
39.89
Expressions(emit→JIT)
67.94
Compiler Service(parse→emit→JIT)
4314
倍遅い
2桁遅い
ある環境で、あるコードの生成結果の例
ポイント
• メタデータを持ってる = 動的コード生成できる• C#ソースコードから : .NET Compiler Service• 式ツリーから : System.Linq.Expressions• IL 直接生成 : ILGenerator
遅い・楽
速い・面倒
※ .NET Native( 後述 ) だと動的コード実行がインタープリター方式になっちゃってかなり遅いので注意
vNextJIT以外の実行方法.NET vNext
• .NET Native
• Cloude Mode
JIT は便利なんだけど
利点•動的リンク・部分更新しやすい•セキュリティ検証しやすい•(ソースコード配布よりは)高速
欠点•.NET Frameworkのランタイム インストール必須•(ネイティブ配布よりは)低速•(ソースコード配布よりは)デバッグ・修正が面倒
用途によっては…
例えば携帯デバイス向け
利点•動的リンク・部分更新しやすい•セキュリティ検証しやすい•(ソースコード配布よりは)高速
欠点•.NET Frameworkのランタイム インストール必須•(ネイティブ配布よりは)低速•(ソースコード配布よりは)デバッグ・修正が面倒
ストア サーバー上でやればいい
ランタイム分のストレージ容量使いたくない
性能的に結構困る
ストア サーバー上でネイティブ化
.NET Native
.NET Native: 事前ネイティブ化• .NET アプリを事前にネイティブ化• 実用上はストア サーバー上でネイティブ化
• セキュリティ保証• アップロード時には IL• 審査付き
• クライアント デバイス的には低コスト• JIT のコストなし• ランタイムのインストール不要
• Visual Studio 上で直接ネイティブ化も可能• 通常版の .NET との差で問題が起きないかの確認• パフォーマンスの確認
.NET Native: 最適化
•「事前に JIT相当の処理」以上の最適化も• シリアライズなどのコードを事前に展開
• 型情報を見たいからリフレクションを使ってただけで、実際にはビルド時に型が確定してることが多い
• 必要な分だけ静的リンク• ライブラリをまたいだ最適化が可能• 不要コードは残さないので実行可能ファイルのサイズは膨らまない
.NET Native: リフレクション• リフレクションも使える
ただし…• XAML {Binding} など、推論が効く部分は自動判
定してくれる• それ以外は、自分で「この型はリフレクションで
使ってる」という情報を書かないとダメ(Runtime Directive ファイル )• 動的コード生成は無理
• 式ツリーなどはインタープリター方式になるので遅い
例えばサーバー向け
利点•動的リンク・部分更新しやすい•セキュリティ検証しやすい•(ソースコード配布よりは)高速
欠点•.NET Frameworkのランタイム インストール必須•(ネイティブ配布よりは)低速•(ソースコード配布よりは)デバッグ・修正が面倒
アプリ稼働時間が長くて、最初の 1 回だけのコストは無視できる (低速でもいい )
開発機とサーバー機でバージョンが違って困ることが
サーバー上で確認しつつその場でソースコードを修正したい
ソースコード配置自動パッケージ管理
Cloud Modeアプリごとに別バージョンを使えない
Cloud Mode:
•ソースコード配置• .NET Compiler Service (Roslyn) を利用
• メモリ上で全部コンパイル ( 一時ファイルを残さない )
• ソースコード書き替えて、ブラウザー更新するだけ
• side-by-side インストール• (サーバーにインストールするんじゃなく )
アプリごとに別 .NET ランタイムを使える• アプリごとにバージョンを変えれる• 開発時と同じバージョンを使える
Cloud Mode: パッケージ管理• 自動パッケージ管理
開発機 アプリ サーバー パッケージ リポジトリ
例http://nuget.orghttp://myget.org
project.json
*.cs
"dependencies": { "Library.A": "", "Library.B": ""}
var c = new Configuration();c.AddJsonFile("config.json");a.UseServices(services => …);
ソースコードとパッケージ利用情報だけをアップ
ロード
サーバー上で編集可能
.NET ランタイムや、ライブラリの不足・更新
はクラウドから取得
利用するパッケージの情報ファイル
ポイント
•選べる実行方法• IL配布 (JIT)
• デスクトップなら割とこれが便利• ネイティブ配布 (.NET Native)
• 携帯デバイス向け• ソースコード配置 (Cloud Mode)
• サーバー向け• 特に共有ホスティングなクラウド サーバー
Portable Class Library複数の「標準ライブラリ」いろんな実行環境の共通部分
問題 : 複数の標準ライブラリ
• マイクロソフト製品だけでも…
デスクトップクライアント アプリ
Phone/ タブレットクライアント アプリ
サーバー アプリ
共通部分
この共通部分だけを「標準」にすべき?
.NET NativeCloud Mode
問題 : 複数の標準ライブラリ
• まして今、 Xamarin (Mono)• Xamarin.iOS 、 Xamarin.Android
WindowsデスクトップWindows
タブレット /Phone
Linuxサーバー
この共通部分だけを「標準」にすべき?
Windowsサーバー
iOS
Android
.NET Framework と Mono
C#
VB
F#
C#
CLR
Monoランタイム
.NET Fullプロファイル
.NET Coreプロファイル
.NET Full(サブセッ
ト )iOS向け
プロファイル
Android向けプロファイル
.NETFramework
Mono
コンパイラー 実行環境 ライブラリ
実行環境にはかなりの互換性がある 標準で使える
ライブラリが違う
どこでも使えそうに見えても…•例えばファイル システム• 最近の Windows はいろんなプロパティを持って
る• 画像ファイルなんかだと :サムネイル画像、撮影日時、画像サイズ、タグ
• 検索インデックスも張ってる
• ストア アプリだとファイル システムすら制限下• ユーザーの許可がないファイルには触れない
こういうものも標準に含めたいか?
一番制限がきつい環境に合わせて標準ライブラリを作るべき?
汎用 VS 特定環境どこでも動く• 最
大公約数
• 動作保証に時間がかかる
特定環境で動く• 高
機能
• 早く提供できる
Portable Class Library
• 実行環境ごとに別の「標準ライブラリ」メタデータを用意• ライブラリ側でどの
実行環境をターゲットにするか選ぶ
実行環境ごとに別メタデータを提供
ターゲット選択
Portable Class Library
•例 : 2環境
共通部分
Portable Class Library
•例 : 3環境
共通部分
.NET vNext
• ぶっちゃけ 3系統別実装 (主用途も別 )• JIT• .NET Native• Cloud Mode
• 1 つの“標準”ライブラリで保守するのは大変• どうしても事前ネイティブ化しにくい…• サーバーに GUI コンポーネント要るの?…• フットプリント的に余計なもの載せれない…
既存品とはいえ、ちゃんと次世代でも更新あり• 64ビット対応強化• SIMD演算対応
new!
new!
PCL みたいな仕組み必須
ポイント
•複数の“標準”• 用途によって
• デスクトップ、携帯デバイス、クラウド• 実装によって
• JIT 、事前ネイティブ化、ソースコード配置• 他 OSへのポーティング
ME
SE
EE
v2.0
v3.0
v4.0
× 包含関係では不十分 ○ 複数の“標準”の共通部分管理
3 時間目• C# 言語機能• generics• iterator• LINQ• dynamic• async/await
• .NET Compiler Platform
1
2
3
C# の歴史
C# 1.0• Man
aged
C# 2.0• Generi
cs• Iterato
r
C# 3.0• LINQ
C# 4.0• Dynamic
C# 5.0• Async
※VB 7~ 11 の歴史でもある
ジェネリックC# 2.0
C++ で言うところの template
C++ template と .NET Generics の違い
型違いの処理
• 型だけ違う処理、コピペで書いていませんか
int Max(int x, int y){ return x > y ? x : y;}
class IntStack{ void Push(int item) { … } int Pop() { … }}
double Max(double x, double y){ return x > y ? x : y;}
class DoubleStack{ void Push(double item) { … } double Pop() { … }}
ジェネリック†
• 型をパラメーター化
† generics。MS翻訳ルール的に、語尾 s は取るんですって複数形の s扱いで。単複の区別のない言語に訳すとき
class Stack<T>{ void Push(T item) { … } T Pop() { … }}
T Max<T>(T x, T y) where T : IComparable<T>{ return x.CompareTo(y) > 0 ? x : y;}
int Max(int x, int y){ return x > y ? x : y;}
class IntStack{ void Push(int item) { … } int Pop() { … }}
型の直行化
• 要素の型と、処理・要素管理は分けましょう
要素の型
処理
intbytedoublestring… Max
MinAverage…
管理方式
ListLinkedListStack…
型の直行化
• 要素の型と、処理・要素管理は分けましょう
要素の型
処理
intbytedoublestring… Max
MinAverage…
管理方式
ListLinkedListStack…
Lパターン Mパターン
Nパターン
分けて作らないと L×M×N 通りのパターン
分けて作ると L+M+N 通り
2.0 からなので…
• 1.0 の頃の名残がちらほら…
• 今となっては
GroupCollectionMatchCollectionAttributeCollectionStringCollection…
IEnumerable<Group>IEnumerable<Match>IEnumerable<Attribute>IEnumerable<string>…
IReadOnlyCollection<Group>IReadOnlyCollection<Match>IReadOnlyCollection<Attribute>IReadOnlyCollection<string>…
もしくは
1 つの型で済む
†
† System.dll に実在する型。ごくごく一部の抜粋。
.NET のジェネリック
• IL レベル対応• メタデータがちゃんと残る
• リフレクションで厳密な型をとれる・区別できる• ジェネリック用命令持ってる
• JIT 時に展開• キャストとかの不要コードが消える
• 値型のボックス化も消える• 値型の展開はかなりパフォーマンスに寄与
おまけ : C++ の template
• コンパイル時に展開• 超高機能なマクロみたいなもの• ○ 実行時のパフォーマンスいい• ○ かなり自由が効く• × 実行可能ファイル サイズが肥大化しがち• × コンパイル エラーが出た時結構悲惨
おまけ : Java のジェネリック
• 型消去• コンパイル時に実は型が消えてる
• 全部 object扱い• ○? Java 1.0 の頃から bytecode命令増えてない• × 実行時にリフレクションで情報とれない• × キャストが挟まる• × 全然違う型にキャストできちゃう ( 実行時例
外 )
いろいろ窮屈な面も
• .NET のジェネリックでは、インターフェイス制約かけないとメソッドすら呼べない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
いろいろ窮屈な面も
•特に困るのが演算子使えないこと• 実体は静的メソッドなので
static T Sum<T>(T[] array){
T sum = default(T);foreach (var x in array) sum += x;return sum;
} コンパイル エラー演算子定義されてない
いろいろ窮屈な面も
• dynamic でごまかせなくはないけども…• パフォーマンス出ないので最後の手段
static T Sum<T>(T[] array){
dynamic sum = default(T);foreach (var x in array) sum += x;return sum;
} 実行時コード生成で+演算子が呼ばれる
ポイント
• 型のパラメーター化• 似て非なるコピペ コードの解消• 型の直行化
• {int, byte, string, …} × {List, Stack, Queue, …}
• .NET のジェネリックは IL レベル対応• メタデータあり
• 実行時にリフレクションで情報とれる• JIT 時に展開
• キャストなどの不要なコードは挟まらない
イテレーターC# 2.0
イテレーター生成用の構文
データの列挙
• プログラム中、データの列挙は非常に多い• データを使う側はすごく楽
• 作る側、加工する側は?
foreach (var x in data){ Console.WriteLine(x);}
データの列挙の例
• substring の列挙• データを作る側と使う側を分けないなら
static void WriteSubstrings(string s){ for (var len = s.Length; len >= 1; len--) for (var i = 0; i <= s.Length - len; i++) Console.WriteLine(s.Substring(i, len));}
いつも Console.Write したいわけじゃないsubstring を使いたいたびに同じコード書くの?
データの列挙の例
• substring の列挙、分けたいなら• ↓こんな感じのクラスを書けばいいんだけど…class GetSubstringEnumerator{ public string Current { get; private set; } string _s; int _len; int _i; public GetSubstringEnumerator(string s) { _s = s; _len = s.Length; _i = 0; } public bool MoveNext() { for (; _len >= 1; _len--, _i = 0) for (; _i <= _s.Length - _len; ) { Current = _s.Substring(_i, _len); _i++; return true; } return false; }}
こういうクラスをイテレーター (iterator) とか列挙子 (enumerator) って言
う作るの結構面倒
イテレーターの例
• 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 を持つ関数ブロッ
ク )イテレーター クラスを自動生成
内部実装 (全体像 )
• クラス生成
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);}
内部実装 ( ローカル変数 )
• ローカル変数 → フィールド
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);}
内部実装 (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);}
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 で囲う
内部実装 ( 中断と再開 )
• yield return の部分、意味合いとしては• 中断と再開
yield return x;
_state = 1;Current = x;return true;case 1:;
状態の記録
現在の値の保持
復帰用のラベル
おまけ : 他の言語だと
•最近の言語は結構似た機能†持ってる• スタック丸ごとキャプチャしてしまうもの
• 中断時に記録、再開時に復元• 式を継続渡しスタイル (CPS) に変換してしまうも
の• スレッド使う ( さすがに性能的に論外だけども )
• 割と機械的な置き換えなので、マクロでも
† 他の言語だとジェネレーター (generator) って呼ばれることが多い
#define BEGIN_ITERATOR\ switch(_state)\ {\ case 0:
#define YIELD(STATE, VALUE)\ _state = STATE;\ _current = VALUE;\ return true;\ case STATE:;
#define END_ITERATOR\ _state = -1;\ default:;\ }\ return false;
おまけ : 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; }}
ちなみに
• コーディング面接で有名な某社の社員さん曰く、
• 例に使ったの 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);}
LINQ(Language Integrated Query)C# 3.0
データ処理の直行化
LINQ
• データ処理を言語統合• ほとんどはライブラリで実現
• System.Linq名前空間
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
Enumerable クラスのWhere メソッドと Select メ
ソッドが呼ばれるだけ
条件を満たす要素だけ残す
要素ごとに処理をかける
データ処理の直行化
• データの入力、加工、出力は分けましょう
ユーザー入力から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…
入力 加工 出力
データ処理の直行化
•掛け算を足し算に
ユーザー入力から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 通り
yield return で実装できる
•列挙子から別の列挙子を作る処理
• ここまでは C# 2.0• これに加えて、 C# 3.0 では…
IEnumerable<R> Select<S, R>( IEnumerable<S> source, Func<S, R> selector){ foreach (var x in source) yield return selector(x);}
IEnumerable<T> Where<T( IEnumerable<T> source, Func<T, bool> predicate){ foreach (var x in source) if (predicate(x)) yield return x;}
列挙子を引数にとって列挙子を返す
yield があれば実装簡単
C# 3.0
• ラムダ式•匿名型•拡張メソッド
ラムダ式
•匿名関数を簡単に書ける
• =>演算子• goes to (~になる )演算子• 左が引数で、右が関数本体• 型推論も効いてる
source.Select(x => x * x);(シーケンスの全要素を二乗 )
そもそも : 匿名関数
• メソッドの自動生成
IEnumerable<int> Triple(IEnumerable<int> input){ return input.Select(x => 3 * x);}
IEnumerable<int> Triple(IEnumerable<int> input){ return input.Select(X);} int X(int x) { return 3 * x; }
ローカル変数のキャプチャ
• 実はクラス生成IEnumerable<int> Multiply(IEnumerable<int> input){ var a = int.Parse(Console.ReadLine()); return input.Select(x => a * x);}
IEnumerable<int> Multiply(IEnumerable<int> input){ var _ = new Anonymous(); _.a = int.Parse(Console.ReadLine()); return input.Select(_.X);}
class Anonymous{ public int a; public int X(int x) { return a * x; }}
ローカル変数がフィールドに昇格
匿名型
• 1 か所でしか使わないような型は作らなくていい
• immutable なクラスを自動生成• GetHashCode 、等値比較、 ToString を完備
source.GroupBy(p => new { p.X, p.Y });(X と Y でグループ化 )
class Anonymous{ public int X { get; private set; } public int Y { get; private set; } public Anonymous(int x, int y) { X = x; Y = y; }}
拡張メソッド
• 静的メソッドを後置き記法で書ける
• 単に語順を変えるだけ
source.Select(x => x * x);
System.Linq.Enumerable.Select( source, x => x * x);
同じ意味
語順を変えるだけで
• 語順のインパクト意外と大きい
var result = data .Where(x => x < 3) .Select(x => x * x) .GroupBy(x => x % 2) .Select(g => g.Sum());
var result1 = Select( GroupBy( Select( Where( data, x => x < 3), x => x * x), x => x % 2), g => Sum(g));
逆順 処理順
( ) が遠い( ) が近い
普通の静的メソッド 拡張メソッド
静的メソッド
• “static” と言ってもいろいろ
static bool globalFlag;いつどこで誰が書き換えるか
わからないのがダメ
静的フィールド (書き換え可能 )
const int Max = 100;static readonly TimeSpan interval = TimeSpan.FromSeconds(1);
定数 or 読み取り専用静的フィールド
書き換え禁止すれば無害
static int Clip(int x) { return Math.Max(x, Max); }
静的メソッド
無害なものにしか触れない限り、無害
静的メソッド
• “static” と言ってもいろいろ
static bool globalFlag;いつどこで誰が書き換えるか
わからないのがダメ
静的フィールド (書き換え可能 )
const int Max = 100;static readonly TimeSpan interval = TimeSpan.FromSeconds(1);
定数 or 読み取り専用静的フィールド
書き換え禁止すれば無害
static int Clip(int x) { return Math.Max(x, Max); }
静的メソッド
無害なものにしか触れない限り、無害
純粋関数 (pure function)書き換えが起こらないメソッド = 同じ引数を与えたら常に同じ結果しか返らない = テストしやすくていい
ポイント
• C# 3.0• ラムダ式• 匿名型• 拡張メソッド
•小さくて、汎用的な機能の集まり• LINQ以外でも有用
LINQ ( データ処理用構文・ライブラリ ) に関連して入った
機能
dynamic 型C# 4.0
ダック タイピング用の型
おさらい : メタデータ
• プロパティ名とかは、本来、実行時に要らない
IL_0000: ldarg.0IL_0001: ldfld int32 T::XIL_0006: ldarg.0IL_0007: ldfld int32 T::YIL_000c: mulIL_000d: ldarg.0IL_000e: ldfld int32 T::ZIL_0013: mulIL_0014: ret
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
IL の状態 実行時 (ネイティブ化した状態 )
名前が残ってる名前が消えて、レイアウト情報だけになってる
ダック タイピング†
• 同じ名前のメンバーを持っていれば同じ型扱いできないか
† アヒルのように歩き、アヒルのように鳴くなら、それはアヒルだ」という論法からきた比喩表現
class Point{ public int X { get; set; } public int Y { get; set; } public void Add(Point p) { X += p.X; Y += p.Y; }}
ダック タイピング†
• 同じ名前のメンバーを持っていれば同じ型扱いできないか
† アヒルのように歩き、アヒルのように鳴くなら、それはアヒルだ」という論法からきた比喩表現
class Point{ public int X { get; set; } public int Y { get; set; } public void Add(Point p) { X += p.X; Y += p.Y; }}
?
X と Y というプロパティ ( またはフィールド ) を持っている任意
の型を使いたい
こういう処理にはリフレクション( メタデータの実行時利用 ) が必要
dynamic 型
•ダック タイピング用の型
class Point{ public int X { get; set; } public int Y { get; set; } public void Add(dynamic p) { X += p.X; Y += p.Y; }}
X と Y というプロパティ ( またはフィールド ) を持っている任意
の型を使える
もちろん、内部的にはリフレクション使ってる
dynamic 型
• 結構用途が限られる• そもそも C# でダック タイピングしたい状況が稀
• ほぼ、外部との連携用• COM との連携• 動的言語との連携• JSON みたいなスキーマレスなデータ読み書き
• その他、例えばできること• 多重ディスパッチ
• できないこと• メタプログラミング• C# のスクリプト的実行
例 : 動的言語との連携
• DLR (Dynamic Language Runtime)• 例 : IronPython
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]);
Python コード
例 : 多重ディスパッチ
• 静的な型に対してclass Base { }class A : Base { }class B : Base { }
例 :多重ディスパッチ
• 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"; }}
動的な呼び出し
例 :多重ディスパッチ
•呼び出し例
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));}
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
動的コード生成用の情報
これが本体
CallSite.Target の中身
• メソッドを動的コード生成してる• 生成したメソッドはキャッシュして持っておく
(inline method cache)
static object _anonymous(CallSite site, object x){ return site.Target を更新する処理}
_site1.Target の初期状態
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 行追加単なる型判定+キャス
ト
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で高速化してる
“heavy” dynamic
• 用途によっては高性能なものの…• 多くの場合、過剰スペック• できるけど過剰スペックな例
• JSON みたいなスキーマレスなデータ読み書き
• もっと” light-weight” な dynamic が必要
p.X;p.M();
p.Get("X");p.Invoke("M");
例 : 規約ベースの置き換え
一時期、「 p.$x を p["x"] と解釈しよう」
という提案はあったけども、結局立ち消え中
できないこと
• メンバー名がコンパイル時に既知でないとダメ
• なのでメタプログラミングには使えない• そのためにはもっと低レイヤーな API 使う
• IL Emit• Expression Tree• Roslyn
dynamic x = p;dynamic y = q;y.X = x.X;y.Y = x.Y;
コンパイル時に既知
ポイント
• dynamic 型• C# でダック タイピング
• 主に外部との連携用• inline method cache
• 用途があえば結構高性能• 用途によっては過剰スペック
• メタプログラミング用ではない
async/awaitC# 5.0
非同期処理
スレッド関連おさらい
• 非同期処理• マルチタスク• UI スレッドを止めない• I/O ( 外の世界との入出力待ち )
• Task クラスを使いましょう• スレッド プール• I/O完了ポート
割と避けれないことなんだけども…
非同期処理しんどい
•普通に非同期処理やったらコールバック地獄• 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 回だか
らこの程度で済んでる
•複数の確認ダイアログ表示
面倒な非同期処理の例
確認 1チェック
確認 2チェック
確認 3チェック
No
No
Yes
Yes
Yes
確認フロー
結果表示
No
ゲームでアイテムを合成します
レア アイテムですよ?
合成強化済みですよ?
もう強化限界ですよ?
ユーザーからの入力待ちも非同期処理
同期処理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;
非同期処理 (旧 )•画面に収まるように
フォント サイズ調整• 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);
非同期処理( 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演算子が増えた
だけ• ダイアログの数が増えても平気
イテレーターと似た仕組み
• イテレーター ( ジェネレーター ) があれば、割と単純なラッパーで非同期処理も可能• 要は、「中断と再開」
• 例えば、 TypeScript では• 現状でも await の MS社内実装は持ってる• いまだと、生成される JavaScript が悲惨すぎて大変• EcmaScript にジェネレーターが実装されてから、
TypeScript に await を追加する予定
参考 : 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:
状態の記録
中断
再開用のラベル
基本的な考え方
•概念としては イテレーター +継続呼び出し
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;
中断
状態の記録
結果の受け取り
再開用のラベル
非同期処理が終わったら続きから呼び出してもらう
実際の展開結果
• 実際はもう少し複雑• 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可能• GetAwaiter は拡張メソッドでも OK
Awaitable
• Awaitable なクラスは自作可能• 実装次第でいろいろ• Windowd Runtime
• IAsyncOperation インターフェイスとかを await できる
• Task の Awaitable 実装• UI スレッドへのディスパッチを内部的にやってくれる• そうしたくない場合の実装もある
• ConfigureAwait
おまけ : 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;}
おまけ : immutable
•並列処理といえば immutable だけども• 書き換えが起こらないなら複数のスレッドで共有
しても安全
• この用途だと、 C++ の const は不十分• あれは参照渡しを安全に行う用であって• 呼び出し元の側では const とは限らず、書き換わ
る可能性あり• あと、 mutable修飾子を付ければ const なオブ
ジェクトすら書き換えれる
ポイント
• 非同期処理は、避けれないんだけども面倒• async/await で多少マシに• 実装的にはイテレーターと同様、中断と再開
.NETCompiler PlatformCompiler as a Service
コンパイラーの内部データを活用
.NET Compiler Platform†
• C#/VB コンパイラーを再設計・再実装• .NET 実装
• C# 実装の C# コンパイラー• VB 実装の VB コンパイラー
• コンパイラーの内部データを誰でも自由に使える
† コードネーム“ Roslyn” って呼ばれてたやつの正式名称
コンパイラーのサービス化、プラットフォーム化
用途
• C# スクリプティング• C# で設定ファイル書きたい ( むしろ XML がい
や )• アプリの再起動なしでロジック更新
•ソースコード配置• .NET vNext, Cloud Mode
• コード解析• コードのインデックス化†• サーバー ビルド、テスト• IDE 連携
現状の最優先事項
† .NET の参照ソースコードが実際やってるhttp://referencesource.microsoft.com/
構文ハイライト
• 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();
}
メソッド名
クラス名
変数 キーワード
リアルタイム エラー検出
•エラー検出タイミングがビルド時とか遅い• Visual Studio は常時やってる
コード補完
• タイピングめんどくさい•文法やライブラリ、いちいち覚えたくない
リファクタリング
•最初からきれいなコード書くのめんどくさい• でもほっときたくない
ということで
• 今、コンパイラーに求められる要件• ソースコードのどこからどこまで(何行何列目)
が何かという情報がとれる• 文脈に応じてソースコードを生成したり、書き替
えたりできる• リアルタイム処理を必要とするので、パフォーマ
ンスも求められる
実はこれまで
• コンパイラー (パーサー ) を 2 重開発してた• コンパイル用• IDE 用• まして、サード パーティ製コード解析プラグイン
も含めると、 3 重開発
• Java (Eclipse とか IntelliJ とか ) でも• (去年ぐらいによく言われてたけども)「 Eclipse が対応するまで Java 8 プレビュー試せないや」
おまけ : 他の環境 (Clang)
• Clang はその辺りをゴールの 1 つに掲げてる• IDE で使う前提• リファクタリングに使いやすいデータ構造の構文木• インクリメンタル コンパイルとかもしやすい
IDE 連携まで考えると
•簡単そうに見える文法ですら、実装コストかなり高い• 2 重開発はそれだけ大変• だから IDE を信用しない / できない人も多い
• IDE対応待ってられない /待ちたくない
• .NET Compiler Platform はそれを解消• C# に新機能を足しやすくなる• C# 6.0
C# 6.0 (予定 ) の例
public class Point(int x, int y){
public int X { get } = x;public int Y { get } = y;
}
Primary Constructor / Property Expressions
while ((var line = stream.ReadLine()) != null)
line ...
if ((var x = obj as Point) != null)x ...
Declaration Expressions
immutable な型を作りやすく
「式」で書けることの幅が広がる
ポイント
• 実行可能ファイルを作るだけがコンパイラーの仕事じゃない• スクリプト実行• コード解析
•特に、 IDE 連携• しかも、リアルタイム処理
• C#/VB コンパイラーの再設計= .NET Compiler Platform
このあたりまで考えるとちょっとした言語機能
を足すのも大変