Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

C#/.NETがやっていること 第二版

35,654 views

Published on

2014/6/28 CLR/H in Tokyo 第3回 にて登壇

Published in: Technology
  • Be the first to comment

C#/.NETがやっていること 第二版

  1. 1. C#や.NET Framework がやっていること 第二版 岩永 信之
  2. 2. 今日の内容 • C#や.NET Frameworkがやっていること • どうしてそういう機能がある • (自前で実装するなら)どうやればできる • (自前でやる上で) 気を付ける必要がある点
  3. 3. 注意 • ベースは3月にBoost勉強会でやった内容 • 通常の3倍速で1時間で話した • 本来、1時間で話す内容じゃない • かなりの前提知識を仮定 • 3時間話します • いくつか追記 • 前提をだいぶ緩めたので背景説明など • .NET vNextがらみ追加 • 5月の非同期勉強会での内容も少しマージ 同じような内容でも回数重ねてこなれてきてたり
  4. 4. 目次 • 1時間目 • メモリ管理 • マルチ タスク管理 • 2時間目 • メタデータ • 3時間目 • C#言語機能 1 2 3
  5. 5. 1時間目 • メモリ管理 • ヒープ自動管理 • 境界チェック • ドメイン分離 • マルチ タスク管理 • スレッドと非同期処理 1 2 3
  6. 6. “Managed” • .NET FrameworkといえばManaged • .NETがいろいろ管理 • メモリ リーク防止 • セキュリティ ホール防止 • v1リリース後、一番進歩が激しいのは非同期 • 効率的なタスク管理 • I/O-boundな非同期処理
  7. 7. Garbage Collection メモリ リークをなくすために
  8. 8. メモリ管理(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
  9. 9. メモリ管理(2) • ヒープ • 連結リスト構造で任意の位置にメモリ確保 • 動的なメモリ利用できる • 管理が大変 • 消し忘れ(メモリ リーク)があり得る 使用中かどうかのフラグ ☑□ □ ☑ 次のヒープの位置 前のヒープの位置 … …
  10. 10. 値型と参照型 • 値型 • intとかbyteとか • structとかenumとか スタック ヒープ 他の型 他の型 スタック上や、他の型の 中に直接埋め込まれる • 参照型 • stringとか • classとかdelegateとか スタック ヒープ スタック上や他の型の中 には参照情報だけある ヒープ上に置かれる
  11. 11. ヒープ管理 • 怠ると • 徐々にアプリが重くなる • 長時間稼働させると突然の死 • その割に大変 • (自動管理がない頃) プログラミングの苦労の半分くらい占める • 本当にやりたいことに注力できてない • 自動化したい 不要なオブジェクトの回収 =ゴミ を集め(garbage collection)
  12. 12. 代表的な手法 • 参照カウント • 生成・コピー代入のたびにカウントを +1 • 変数がスコープ外れるたびに -1 • カウントが0になったらメモリ削除 • Mark & Sweep • ルート†からたどれるオブジェクトに印を付ける • 印のついてないオブジェクトを削除 † スタックや静的フィールド中のオブジェクトなど 参照の起点となる場所 mark sweep
  13. 13. Mark & Sweep • ルートから参照をたどる Mark ヒープ ルート Sweep 使用中オブジェクト 未使用オブジェクト
  14. 14. Compaction • GC時にオブジェクトの位置を移動 使用中オブジェクト 未使用オブジェクト 隙間をなくすように移動 後ろが丸ごと空くので次のメモリ確保が楽
  15. 15. Generation • 移動したオブジェクトはしばらくノータッチ Gen1 Gen0 Gen0 しばらくここは ノータッチ この範囲でだけ メモリ確保・GC オブジェクトの寿命 統計的に • 短いものはとことん短く • 長いものはとことん長い 一度GCかかったオブジェクト はしばらく放置
  16. 16. .NET FrameworkのGC • .NET FrameworkのGCはMark & Sweep • Compactionあり • 世代別 (3世代、Gen0~2) • Backgroundスレッドで並列実行
  17. 17. Mark & Sweepと参照カウント • 比較 • 一長一短ある Mark & Sweep 参照カウント メモリ確保 ○ 末尾を見るだけ × 空いている場所を探す × カウント用の領域が追加 で必要 変数のコピー ○ ポインターのコピー × ポインターのコピーに加 え、参照数のカウント アップ メモリ解放 × MarkやCompactionに 時間がかかる × 負担が1か所に集中 ○ カウントが0になった時 にdeleteするだけ × 循環参照が苦手
  18. 18. Throughput • トータルの性能(throughput)はMark & Sweep の方がいい Mark & Sweep 参照カウント メモリ確保 ○ 末尾を見るだけ × 空いている場所を探す × カウント用の領域が追加 で必要 変数のコピー ○ ポインターのコピー × ポインターのコピーに加 え、参照数のカウント アップ メモリ解放 × MarkやCompactionに 時間がかかる × 負担が1か所に集中 ○ カウントが0になった時 にdeleteするだけ × 循環参照が苦手 まとめてやる方がバラバラに やるよりスループットはいい 頻度が高い操作なので ここの負担が大きいと 全体の性能落ちる 特に、スレッド安全を求めるときつい たかがインクリメントでも、atomic性 保証するとそこそこの負担 参照局所性も高くなって キャッシュが効きやすい
  19. 19. 短所の軽減 • 短所も、まったく打つ手がないわけじゃない Mark & Sweep 参照カウント メモリ確保 ○ 末尾を見るだけ × 空いている場所を探す × カウント用の領域が追加 で必要 変数のコピー ○ ポインターのコピー × ポインターのコピーに加 え、参照数のカウント アップ メモリ解放 × MarkやCompactionに 時間がかかる × 負担が1か所に集中 ○ カウントが0になった時 にdeleteするだけ × 循環参照が苦手 .NET 4以降、Background スレッド実行してる C++ 11的には 「move semantics活用 してね」
  20. 20. 自前メモリ管理との混在 • スコープが短いならスタックにとればいいのに • C#はclassは必ずヒープ、structはスタックになる • 使い分けれない • Mark & Sweep管理領域内のポインターを管理 外に渡すときには注意が必要 • Compactionでメモリ移動しないように 「ピン止め」が必要 • 結構ガベージ コレクションのパフォーマンス落とす
  21. 21. おまけ: 他の言語 • C++: 主に参照カウント方式のライブラリ利用 • 型がはっきりしない言語ではMark & Sweepしにくい • できなくはないけども「保守的」になる(効率落とす) • 数値を無差別にポインター扱いしてMarkする • Pythonとか: 参照カウントとMark & Sweepの併用 • ○ 循環参照問題避けつつ、負担を1か所に集中させない • × 性能的には悪いとこどり • Go: 割当先を自動判別 • スコープ内で完結してたらスタックに、さもなくばヒープに • これはこれでスタックの浪費激しそうなんだけども… • なので、可変長スタック持ってる
  22. 22. 注意: GCあってもリーク • 長時間生きているオブジェクト†が参照持った ままになると、GC対象になれない † ワーストケースはアプリ稼働中ずっと生きているオブジェクト class View : UserControl { public View(Model model) { model.PropertyChanged += (sender, e) => { // データの表示を更新 }; } } よく起こり得る例 ビューは画面遷移で消える モデルはずっと生きてる イベント購読で、 モデルがビューの参照持つ 解除 (-=) しないとメモリ リーク
  23. 23. ポイント • メモリ管理は大変なので自動化 • Mark & Sweep • いろいろ賢いアルゴリズム搭載してるし、ほとんど の場合、任せた方がいい • GCあってもメモリ リークは起こり得るので注 意
  24. 24. 境界チェック 意図しないメモリ領域にはアクセスさせない
  25. 25. 配列の境界チェック • .NETの配列は厳しい • 常に境界チェックしてる • 要はbuffer overrun†防止 • ちなみにJIT最適化で不要な境界チェック消してる (明らかに境界を侵さないものはチェック不要) • for (var i; i < a.Length; ++i) とか • foreach (var item in a) とか 配列 a OutOfRange OutOfRange a[-1] a[length+1] † クラッシュやセキュリティ ホールの温床
  26. 26. unsafe • とはいえ、C#にもポインターあるんだけども var x = new[] { 1, 2, 3, 4, 5 }; unsafe { fixed(int* px = &x[0]) { Console.WriteLine(px[100]); } } メモリ移動の防止 (ピン止め) 範囲チェックなし 安全でないコード この中でだけポイ ンター利用可能 buffer overrunやりたい放題
  27. 27. unsafeでも制限付き • class (参照型)はアドレス取れない • 仮想メソッド テーブル(vtable)とか入ってるし • 親クラス側のレイアウト変更の影響受けるし class Class { public int x; public int y; } var c = new Class(); fixed (void* p = &c) { } // error fixed (void* p = &c.x) { } // OK fixed (void* p = &c.y) { } // OK ピン止め必須 • 値型のメンバーのアドレスは取れる • オブジェクト自体のアドレスは取れ ない
  28. 28. unsafeでも制限付き • struct (値型)はアドレス取れる • ただし、メンバーが全部値型の時のみ† • ↑「unmanaged型」と呼ぶ struct UnmanagedStruct { public int x; public int y; } var u = new UnmanagedStruct(); void* p1 = &u; // OK void* p2 = &u.x; // OK void* p3 = &u.y; // OK メンバーが 全部値型 無制限にアドレス取れる † 正確には、再帰的に全子要素が値型、かつ、型パラメーターも値型
  29. 29. unsafeでも制限付き • struct (値型)であっても制限付き • メンバーに1つでも参照型を含むとダメ • ↑「managed型」と呼ぶ struct ManagedStruct { public int x; public string y; } var u = new ManagedStruct(); void* p1 = &u; // error void* p2 = &u.x; // OK void* p3 = &u.y; // error メンバーに 参照型が1つ 本体のアドレス取れない 値型のメンバーのところ だけはアドレス取れる
  30. 30. ポイント • unsafe • 危険なことは基本認めない • 面倒な追加の構文を要求 • コンパイル オプションでも/unsafeの明記必須 • fixed • ガベージ コレクションとの兼ね合い • Compaction阻害になるので注意 • 「Unmanaged型」に限る • 実装依存な部分(vtableとか)や、 型の定義側の変更が利用側に極力影響しないように
  31. 31. AppDomain 実行環境の分離 セキュリティ保証
  32. 32. コンポーネントの連携 • 他のアプリの機能を自分のアプリから呼びたい • WordとかExcelとかのドキュメントを出力 • サーバー上に自分のアプリを配置したい • IIS上に(ASP.NET) • SQL Server上に • (必ずしも)信用できない • セキュリティ保証 • 参照先のクラッシュにアプリ/サーバー が巻き込まれないように • コンポーネントのバージョン アップ
  33. 33. AppDomain • 単一プロセス内で、分離された複数の実行領域 (domain)を提供 • 「分離」 • plugin: 動的なロード/アンロード • security: AppDomainごとに異なる権限付与 • isolation: 別メモリ空間 AppDomain 1 AppDomain 2… プロセス 必ずしも信用できないコードを 安全に、動的に呼び出し
  34. 34. ドメイン間には壁がある • 別メモリ空間に分離されてる AppDomain 1 AppDomain 2 互いに独立
  35. 35. ドメイン間の通信 • マーシャリング(marshaling) • marshal (司令官、案内係)の命令通りにしか壁を超 えれない AppDomain 1 AppDomain 2 • 司令がダメといったら通れない • 司令の指示通りの形式にいったん シリアライズしないといけない
  36. 36. AppDomain 1 AppDomain 2 ドメイン間の通信 • ダメな例 これをBに渡し たいとして あるオブジェクト
  37. 37. AppDomain 1 AppDomain 2 ドメイン間の通信 • ダメな例 shallow copyなんてしようもんなら A側のメモリへの参照が残る
  38. 38. AppDomain 1 AppDomain 2 ドメイン間の通信 • よい例 serialize deserialize { {1, 2}, {3, 4}, {5, 6} } この辺りがマーシャリング 一度シリアライズ 必ずdeep copy
  39. 39. .NETのマーシャリング • 2種類 • marshal by ref • 参照を渡すんだけど、メモリ領域は直接触れない • メソッド越しにしか操作しちゃいけない • 「このメソッドを呼んでくれ」っていうメッセージだけ がドメインを超えてわたって、実際の実行は相手側ドメ インで(プロキシ実行) • marshal by value • 値をシリアライズして相手側に渡す • 規定では、BinarySeralizerを使用 privateフィールドまで含めてリフレクションで 内部状態を取得してシリアライズ
  40. 40. .NETのマーシャリング(文字列) • 文字列は特殊扱い (文字コード変換が不要な場合) • marshal by valueみたいにシリアライズを経ない • marshal by referenceみたいにプロキシ実行しない • 参照を渡して、メモリを直接読んでもらう • immutableかつrange-check付きに作ってある • 変更不可なので参照を渡しても安全 • COM (ネイティブ)に対して文字列を渡す時すらこの方式
  41. 41. AppDomain 1 AppDomain 2 その他のコスト • マーシャリング以外にもいくらかコストが • 例えばライブラリの読み込み Library X Library X 別ドメインに全く同じライブラリを読み込んでも、 それぞれ別イメージが作られる Global Assembly Cache (GAC)にあるDLLだけは別 GAC中のは同じメモリ イメージが共有される
  42. 42. おまけ: 他の言語 • COMなんかはマーシャリングやってる • というか「COMの頃から」やってる • .NETの前身なので • さもなくば、プロセス分離してプロセス間通信 • OS特権がないと他プロセスのメモリにはアクセス できない • 安全だけどかなり高負荷 • RPC†とか一時期流行ったけども廃れた † ただの関数呼び出しに見えるコードで、内部的にプロセス間通信する 知らないうちに性能落とすとか、リモート側でのエラーが処理しにくいとか
  43. 43. ポイント • プラグインとかやるなら「分離」が必須 • とはいえ、プロセス分けると負担が大きすぎる • プロセス内で分離を保証する仕組み • AppDomain • マーシャリング • それなりにコストがかかるけども、セキュリティに は変えられない
  44. 44. スレッド 応答性のよいプログラムを書くために OSやアプリ全体をフリーズさせないために
  45. 45. マルチタスク • コンピューター内で複数のタスクが同時に動作 • CPUコア数に制限されない タスク1 タスク2 タスク3 … タスクの動作期間 実際にCPUを使って 動いている期間 1つのCPUコアを複数の タスクがシェアしてる 問題は • どうやって他のタスクにCPUを譲るか • 誰がどうスケジューリングするか
  46. 46. 2種類のマルチタスク † preemptive: 専買権を持つ、横取りする ※cooperative • ハードウェア タイマーを使って強制割り込み • OSが特権的にスレッド切り替えを行う • ○利点: 公平 (どんなタスクも等しくOSに制御奪われる) • ×欠点: 高負荷 (切り替えコストと使用リソース量が多い) プリエンプティブ† • 各タスクが責任を持って終了する • 1つのタスクが終わるまで次のタスクは始まらない • ○利点: 低負荷 • ×欠点: 不公平 (1タスクの裏切りが、全体をフリーズさせる) 協調的※ なのでスレッドはこっち これが致命的 ただ、問題はこれ
  47. 47. 問題: スレッドは高コスト† • 細々としたタスクを大量にこなすには向かない for (int i = 0; i < 1000; i++) { var t = new Thread(Worker); t.Start(); } 大量の処理をスレッド実行 リソース消費大 切り替え頻発 … † スレッドごとに数MBのメモリを確保したり スレッド切り替えにOS特権が必要だったり 文脈が切り替わっちゃうのでキャッシュ ミスしたり
  48. 48. 解決策: スレッド プール • スレッドを可能な限り使いまわす仕組み • プリエンプティブなスレッド数本の上に • 協調的なタスク キューを用意 スレッド プール キュー タスク1 タスク2 … 数本※のスレッ ドだけ用意 空いているスレッドを探して実行 (長時間空かない時だけ新規スレッド作成) 新規タスク タスクは一度 キューに溜める ※ 理想的にはCPUのコア数分だけ
  49. 49. スレッド プールの性能向上 • Work Stealing Queue • lock-free実装†なローカル キュー • できる限りスレッド切り替えが起きない作り ローカル キュー1 ローカル キュー2 スレッド1 スレッド2 グローバル キュー ① スレッドごとに キューを持つ まず自分用の キューからタスク実行 ② ローカル キュー が空のとき、 他のスレッドから タスクを奪取 † 今回は詳細割愛
  50. 50. スレッド プールの性能向上 • Work Stealing Queue • lock-free実装†なローカル キュー • できる限りスレッド切り替えが起きない作り ローカル キュー1 ローカル キュー2 スレッド1 スレッド2 グローバル キュー ① スレッドごとに キューを持つ まず自分用の キューからタスク実行 ② ローカル キュー が空のとき、 他のスレッドから タスクを奪取 ポイント • スレッドは高コスト • Threadクラスはこっち • スレッド プールの利用推奨 • Taskクラスはこっち † 今回は詳細割愛
  51. 51. 問題: CPUの外の世界は遅い • 実行速度が全然違う ALU メイン・メモリ CPU 周辺機器 数千~ 下手すると数万、数億倍遅い >>>
  52. 52. 2種類の負荷 • CPU-bound (CPUが性能を縛る) • マルチコアCPUの性能を最大限引き出したい • UIスレッドを止めたくない • I/O-bound (I/O※が性能を縛る) • ハードウェア割り込み待つだけ • CPUは使わない • スレッドも必要ない ※ Input/Output: 外部ハードウェアとのやり取り(入出力)
  53. 53. I/O完了待ち • I/O-boundな処理にスレッドは不要 あるスレッド 要求 応答 この間何もしないのに スレッドを確保し続け るのはもったいない
  54. 54. 解決策: I/O完了ポート※ • スレッドを確保せずI/Oを待つ仕組み • コールバックを登録して、割り込みを待つ • コールバック処理はスレッド プールで スレッド プール タスク1 タスク2 … ※ I/O completion port あるスレッドアプリ I/O完了ポート ハードウェア I/O開始 I/O完了 コールバック 登録 コールバック登録後、 すぐにスレッド上での 処理を終了 割り込み信号
  55. 55. I/O完了ポート※ • スレッドを確保せずI/Oを待つ仕組み • コールバックを登録して、割り込みを待つ • コールバック処理はスレッド プールで スレッド プール タスク1 タスク2 … ※ I/O completion port あるスレッドアプリ I/O完了ポート ハードウェア I/O開始 I/O完了 コールバック 登録 コールバック登録後、 すぐにスレッド上での 処理を終了 割り込み信号 ポイント • I/O-boundな処理にスレッドを使っちゃダメ • I/O用の非同期メソッドが用意されてる (内部的にI/O完了ポートを利用)
  56. 56. 問題: スレッド安全保証 • スレッド安全なコードは高コスト • いっそ、単一スレッド動作を前提に • メッセージ ポンプ MSG msg; while (PeekMessage(&msg, (HWND)NULL, 0, 0, PM_REMOVE)) { TranslateMessage(&msg); DispatchMessage(&msg); } こんな感じのループが単一スレッドで動作 他のスレッドからメッセージを キュー越しに受け取って処理
  57. 57. 典型例: UIスレッド • GUIはシングル スレッド動作(UIスレッド)† • ユーザーからの入力受け付け • 画面の更新 UIスレッド ユーザー からの入力 OK グラフィック 更新 他のスレッド 処理中は 応答不可 他のスレッドから は更新不可 † C#/.NETに限らず、だいたいの言語・環境でGUIはシングル スレッド動作
  58. 58. 矛盾 単一スレッドからしか UI更新できない そのスレッドを止める とUIフリーズ シングル スレッド推奨 マルチ スレッド推奨 OK
  59. 59. 解決策: メッセージ配送 1. スレッド プールで重たい処理 2. UIスレッドに処理を戻してからUI更新 UIスレッド OK 更新 他のスレッド 重たい処理 Dispatcher.Invoke Task.Run ※ dispatcher: 配送者 渡す役割を担うのが ディスパッチャー※ 他のGUIフレームワークだとevent queueとかhandlerとかいう 名前で提供されたりするものの、やってることは一緒 いったんUIスレッド にメッセージを渡す
  60. 60. おまけ: 他の言語 • スクリプト言語の類はだいたいスレッド機能 持ってない • GUIを前提にしていないか • GUIがフリーズするほどの処理を書くことを前提に していないか • C++とかは最近(11で)ようやく標準化 • それまでは、スレッドは結構環境依存
  61. 61. ポイント • 応答性と全体のパフォーマンスとの兼ね合い • スレッド プール • I/O待ちのためにスレッドは使っちゃダメ • I/O完了ポート • スレッド安全保証はそれなりに高コスト • シングル スレッドでメッセージ ポンプ • メッセージ配送 Task クラス
  62. 62. 2時間目 • メタデータ • 動的リンク • JIT • PCL 1 2 3
  63. 63. メタデータとは • 実行可能ファイル中には本来不要なデータ • プログラムを作るためのデータ • 実行に必要なデータよりもメタ(高次) 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では、こういうメタデータを実行可能ファイルに残す
  64. 64. .NETのメタデータ • .NETのメタデータ(≒ 型情報) • DLLにどういう型が含まれるか • どういう型がどういうメンバーを持っているか • 外部のどういうDLLを参照しているか • バージョン情報 • DLLにメタデータを残すことで • プログラミング言語をまたげる • 動的リンクでのバージョン管理ができる
  65. 65. おまけ: 昔と今 • COMの頃 • メタデータを自分で書いてた (自動化するツールあるけど) • .tlb/.olbファイル • .NET Framework • .NET自体がメタデータの規格を持ってる • C#とかをコンパイルするだけで作られる
  66. 66. おまけ: C++/CX • C++ Component Extensions • マイクロソフトのC++拡張 C++/CX 素の標準 C++コード COMコード メタデータ (winmd) コンパイル C++内で完結して使う分 にはオーバーヘッドなし COMを呼べる言語なら 何からでも呼べる .NETのメタデータと互換 .NETから簡単に呼べる
  67. 67. 動的リンク 実行可能ファイルにメタデータが含まれていることで
  68. 68. ライブラリ共有 • 前提: ライブラリは共有する アプリA アプリB ライブラリX ライブラリY version 1 version 1
  69. 69. 動的リンク • ライブラリ単体での差し替え • セキュリティ ホールや、致命的なバグの修正 アプリA アプリB ライブラリX ライブラリY version 2 version 1 更新不要 差し替え アプリ実行時にライブラリをリンクしなおしてる = 動的リンク
  70. 70. いまどきの事情 • ライブラリはアプリのパッケージに同梱 • 動的なリンクいるの? アプリA ライブラリX ライブラリY アプリB ライブラリX ライブラリY アプリAパッケージ アプリBパッケージ 別バイナリ・別々に配布 別バージョンでも困らない とはいえ…
  71. 71. 差分ダウンロード ※ 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 インストール機 バージョンアップ時 更新 ここだけ新しい
  72. 72. アプリB ライブラリX ライブラリY アプリ間でライブラリ共有 ※ Windowsストア アプリはこういう仕組み持って アプリA ライブラリX ライブラリY アプリAパッケージ ライブラリY アプリBパッケージ 差分 アプリA インストール機アプリB アプリBインストール時 X、Yは同じものを共有 (ハード リンク作るだけ) version 1 version 1 version 1 version 1 同じバージョン
  73. 73. パッケージ管理 ※ ASP.NET vNextはこういう仕組み持ってる 開発機 アプリ サーバー パッケージ リポジトリ 例 http://nuget.org http://myget.org project.json *.cs "dependencies": { "Library.A": "", "Library.B": "" } var c = new Configuration(); c.AddJsonFile("config.json"); a.UseServices(services => …); ソースコードだけを アップロード サーバー上で 編集可能 ライブラリの不足・更新 はクラウドから取得
  74. 74. ポイント • 動的にリンク • 部分更新・差分ダウンロード • ライブラリ共有 • パッケージ管理 • リンクに必要な情報(メタデータ)を実行可能 ファイルに残す • メタデータも規格化されてる • C#/.NETの場合はコンパイルするだけで作られる
  75. 75. JIT (Just-in-Time compile) 実際の動的リンクの挙動
  76. 76. 中間コードとJITコンパイル • .NET Frameworkの中間言語 • 高級言語のコンパイラーを作る人と、CPUごとの最 適化する人の分業化 • セキュリティ チェックしやすくなったり • 動的リンク時に、コード修正の影響をJITで吸収 高級言語 (C#など) 中間言語 (IL) ネイティブ コード ビルド時に コンパイル Just-in-Time コンパイル JITである必要ない LLVMとかでも中間言語介してコンパイルしてる ストア審査ではじくとか他にも手段はある
  77. 77. 例 • (C#で)こんな型があったとして • 整数のフィールドを3つ持つ public struct Point { public int X; public int Y; public int Z; }
  78. 78. 例 • こんなメソッドを書いたとする • フィールドの掛け算 static int GetVolume(Point p) { return p.X * p.Y * p.Z; }
  79. 79. 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 } 型とかフィールド の名前がそのまま 残ってる 型情報 メタデータ
  80. 80. ネイティブ コード • 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とかの数値に 型情報は残らない
  81. 81. メモリ レイアウト • この4とか8の意味 public struct Point { public int X; public int Y; public int Z; } Point X Y Z 4バイト 8バイト ※レイアウトがどうなるかは環境依存
  82. 82. メモリ レイアウト • この4とか8の意味 public struct Point { public int X; public int Y; public int Z; } Point X Y Z 4バイト 8バイト ※レイアウトがどうなるかは環境依存 ILの時点までは名前 で参照してる ネイティブ コードは レイアウトを見て 数値で参照してる
  83. 83. 数値でのフィールド参照 • 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)
  84. 84. 変更してみる • 大して影響しなさそうな ほんの些細な変更をしてみる public struct Point { public int X; public int Y; public int Z; } public struct Point { public int X; public int Z; public int Y; } フィールドの順序変更
  85. 85. その結果起きること • メモリ レイアウトが変わる※ Point X Y Z Point X Z Y ※ この例(フィールド変更)以外でも、仮想メソッド テーブル とかいろいろレイアウトが変わるものがある
  86. 86. ILレベルでの影響 • 影響なし 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が吸収してくれる
  87. 87. ネイティブ レベルでの影響 • ここで影響が出る 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 更新が必要 利用側の再コンパイルが必要 ライブラリ側だけの差し替えじゃダメ
  88. 88. ただし… • この役割に焦点を当てるなら… • 毎回毎回JITする必要ない • 全部が全部ILな必要ない
  89. 89. Ngen • Ngen.exe • Native Image Generator • ILを事前にネイティブ化するためのツール • 自前管理が必要 • アプリのインストーラー※とかを作って明示的に呼び出し • 参照しているライブラリが更新された時には呼びなおす 必要あり • かなり面倒なのでアプリを Ngenすることはめったにない • .NET自体が標準ライブラリの 高速化のために使ってる ※ 要するに、JITの負担を起動時じゃなくてインストール時に前倒しする
  90. 90. Auto-Ngen • .NET Framework 4.5以降なら • NgenがWindowsサービスとして常に動いてる • アイドル時に動作 • 利用頻度の高いものを自動的にNgen • デスクトップ アプリの場合はGACアセンブリのみ • Windowsストア アプリの場合はすべてのアセンブリ • よく使うアプリの起動はだいぶ早くなる • インストール直後の起動は相変わらず遅い
  91. 91. 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 参照しているライブラリ のレイアウトが変わった 時に再コンパイルが必要
  92. 92. 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::X int32 Point::Y int32 Point::Z ほぼネイティブ レイアウトのところ だけ抽象的に型情報 を残しておく MDIL: Machine Dependent Intermediate Language
  93. 93. MDIL (Compile in the Cloud) • ストア サーバー上でMDIL化までやっておく C#コード IL MDIL ネイティブ コード C#コンパイラー MDILコンパイラー リンカー 開発環境でILにコンパイル Windowsストア サーバー 上でILをMDIL化 Windows Phone実機上では レイアウトの解決(リンク)だけ行う インストール直後の起動も高速 ※ Windows Phoneアプリはこういう仕組み持ってる
  94. 94. ポイント • JITの主な利点 • 動的リンクしやすい • (後述の、動的コード生成しやすいとかもある) • ただし、常にJust-In-Timeな必要ない • インストール時 → Ngen • サービスで定期的に → Auto-Ngen • ストア サーバー上で → Compile in the Cloud
  95. 95. vNextはJITだけじゃない • (詳細は後述) 選べる実行形態 • 事前に完全ネイティブ化 → .NET Native • ソースコード配置 → Cloud Mode
  96. 96. リフレクション 動的コード生成
  97. 97. メタデータ利用 • メタデータ(プログラム生成に必要な情報)を 持っているということは • 動的にプログラム コードを生成できる • 自己反映的動作(リフレクション)
  98. 98. コード生成API 高級言語 (C#) 構文木 IL ネイティブ コード parse emit JIT .NET Compiler Service† 式ツリー (System.Linq.Expressions) ILGenerator † 旧称(コードネーム)Roslyn 動的なコンパイルが可能
  99. 99. C#コードから • .NET Compiler Serviceの例 session.Execute<Func<Point, int>>("p => p.X * p.Y * p.Z") C#ソースコードをコンパイル C#→[parse]→構文木→[emit]→IL→[JIT]→Native
  100. 100. 構文木から • System.Linq.Expressions Expression.Lambda<Func<Point, int>>( Expression.Multiply( Expression.Multiply( Expression.Field(p, x), Expression.Field(p, y)), Expression.Field(p, z)),
  101. 101. 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);
  102. 102. コード生成にかかる時間 • 本当はあんまり動的にやりたくない • 当たり前だけどparseは遅い • なので、.NETはソースコード配布じゃなくてIL配布 • できる限りコンパイル時生成したい かかった時間[ミリ秒] ILGenerator (JIT) 39.89 Expressions (emit→JIT) 67.94 Compiler Service (parse→emit→JIT) 4314 倍遅い 2桁遅い ある環境で、あるコードの生成結果の例
  103. 103. ポイント • メタデータを持ってる = 動的コード生成でき る • C#ソースコードから: .NET Compiler Service • 式ツリーから: System.Linq.Expressions • IL直接生成: ILGenerator 遅い・楽 速い・面倒 ※ .NET Native(後述)だと動的コード実行が インタープリター方式になっちゃって かなり遅いので注意
  104. 104. vNext JIT以外の実行方法 .NET vNext • .NET Native • Cloude Mode
  105. 105. JITは便利なんだけど 利点 • 動的リンク・部分更新しやすい • セキュリティ検証しやすい • (ソースコード配布よりは)高速 欠点 • .NET Frameworkのランタイム インストール必須 • (ネイティブ配布よりは)低速 • (ソースコード配布よりは)デバッグ・修正が面倒 用途によっては…
  106. 106. 例えば携帯デバイス向け 利点 • 動的リンク・部分更新しやすい • セキュリティ検証しやすい • (ソースコード配布よりは)高速 欠点 • .NET Frameworkのランタイム インストール必須 • (ネイティブ配布よりは)低速 • (ソースコード配布よりは)デバッグ・修正が面倒 ストア サーバー上で やればいい ランタイム分のストレージ 容量使いたくない 性能的に結構困る ストア サーバー上で ネイティブ化 .NET Native
  107. 107. .NET Native: 事前ネイティブ化 • .NETアプリを事前にネイティブ化 • 実用上はストア サーバー上でネイティブ化 • セキュリティ保証 • アップロード時にはIL • 審査付き • クライアント デバイス的には低コスト • JITのコストなし • ランタイムのインストール不要 • Visual Studio上で直接ネイティブ化も可能 • 通常版の.NETとの差で問題が起きないかの確認 • パフォーマンスの確認
  108. 108. .NET Native: 最適化 • 「事前にJIT相当の処理」以上の最適化も • シリアライズなどのコードを事前に展開 • 型情報を見たいからリフレクションを使ってただけで、 実際にはビルド時に型が確定してることが多い • 必要な分だけ静的リンク • ライブラリをまたいだ最適化が可能 • 不要コードは残さないので実行可能ファイルのサイズは 膨らまない
  109. 109. .NET Native: リフレクション • リフレクションも使える ただし… • XAML {Binding}など、推論が効く部分は自動判定し てくれる • それ以外は、自分で「この型はリフレクションで 使ってる」という情報を書かないとダメ (Runtime Directiveファイル) • 動的コード生成は無理 • 式ツリーなどはインタープリター方式になるので遅い
  110. 110. 例えばサーバー向け 利点 • 動的リンク・部分更新しやすい • セキュリティ検証しやすい • (ソースコード配布よりは)高速 欠点 • .NET Frameworkのランタイム インストール必須 • (ネイティブ配布よりは)低速 • (ソースコード配布よりは)デバッグ・修正が面倒 アプリ稼働時間が長くて、 最初の1回だけのコストは 無視できる(低速でもいい) 開発機とサーバー機でバー ジョンが違って困ることが サーバー上で確認しつつ その場でソースコードを修正したい ソースコード配置 自動パッケージ管理 Cloud Mode アプリごとに別バー ジョンを使えない
  111. 111. Cloud Mode: • ソースコード配置 • .NET Compiler Service (Roslyn)を利用 • メモリ上で全部コンパイル(一時ファイルを残さない) • ソースコード書き替えて、ブラウザー更新するだけ • side-by-sideインストール • (サーバーにインストールするんじゃなく) アプリごとに別.NETランタイムを使える • アプリごとにバージョンを変えれる • 開発時と同じバージョンを使える
  112. 112. Cloud Mode: パッケージ管理 • 自動パッケージ管理 開発機 アプリ サーバー パッケージ リポジトリ 例 http://nuget.org http://myget.org project.json *.cs "dependencies": { "Library.A": "", "Library.B": "" } var c = new Configuration(); c.AddJsonFile("config.json"); a.UseServices(services => …); ソースコードとパッ ケージ利用情報だけ をアップロード サーバー上で 編集可能 .NETランタイムや、 ライブラリの不足・更新 はクラウドから取得 利用するパッケージ の情報ファイル
  113. 113. ポイント • 選べる実行方法 • IL配布(JIT) • デスクトップなら割とこれが便利 • ネイティブ配布(.NET Native) • 携帯デバイス向け • ソースコード配置(Cloud Mode) • サーバー向け • 特に共有ホスティングなクラウド サーバー
  114. 114. Portable Class Library 複数の「標準ライブラリ」 いろんな実行環境の共通部分
  115. 115. 問題: 複数の標準ライブラリ • マイクロソフト製品だけでも… デスクトップ クライアント アプリ Phone/タブレット クライアント アプリ サーバー アプリ 共通部分 この共通部分だけを 「標準」にすべき? .NET Native Cloud Mode
  116. 116. 問題: 複数の標準ライブラリ • まして今、Xamarin (Mono) • Xamarin.iOS、Xamarin.Android Windows デスクトップWindows タブレット/Phone Linux サーバー この共通部分だけを 「標準」にすべき? Windows サーバー iOS Android
  117. 117. .NET FrameworkとMono C# VB F# C# CLR Mono ランタイム .NET Full プロファイル .NET Core プロファイル .NET Full (サブセット) iOS向け プロファイル Android向け プロファイル .NET Framework Mono コンパイラー 実行環境 ライブラリ 実行環境にはかなり の互換性がある 標準で使える ライブラリが違う
  118. 118. どこでも使えそうに見えても… • 例えばファイル システム • 最近のWindowsはいろんなプロパティを持ってる • 画像ファイルなんかだと: サムネイル画像、撮影日時、画像サイズ、タグ • 検索インデックスも張ってる • ストア アプリだとファイル システムすら制限下 • ユーザーの許可がないファイルには触れない こういうものも標準に含めたいか? 一番制限がきつい環境に合わせて標準ライブラリを作るべき?
  119. 119. 汎用 VS 特定環境 どこでも動く • 最大公約数 • 動作保証に時間が かかる 特定環境で動く • 高機能 • 早く提供できる
  120. 120. Portable Class Library • 実行環境ごとに 別の「標準ライブラリ」 メタデータを用意 • ライブラリ側でどの 実行環境をターゲットに するか選ぶ 実行環境ごとに 別メタデータを提供 ターゲット選択
  121. 121. Portable Class Library • 例: 2環境 共通部分
  122. 122. Portable Class Library • 例: 3環境 共通部分
  123. 123. .NET vNext • ぶっちゃけ3系統別実装 (主用途も別) • JIT • .NET Native • Cloud Mode • 1つの“標準”ライブラリで保守するのは大変 • どうしても事前ネイティブ化しにくい… • サーバーにGUIコンポーネント要るの?… • フットプリント的に余計なもの載せれない… 既存品とはいえ、ちゃんと次世代でも更新あり • 64ビット対応強化 • SIMD演算対応 new! new! PCLみたいな仕組み必須
  124. 124. ポイント • 複数の“標準” • 用途によって • デスクトップ、携帯デバイス、クラウド • 実装によって • JIT、事前ネイティブ化、ソースコード配置 • 他OSへのポーティング ME SE EE v2.0 v3.0 v4.0 × 包含関係では不十分 ○ 複数の“標準”の共通部分管理
  125. 125. 3時間目 • C#言語機能 • generics • iterator • LINQ • dynamic • async/await • .NET Compiler Platform 1 2 3
  126. 126. C#の歴史 C# 1.0 • Managed C# 2.0 • Generics • Iterator C# 3.0 • LINQ C# 4.0 • Dynamic C# 5.0 • Async ※VB 7~11の歴史でもある
  127. 127. ジェネリック C# 2.0 C++で言うところのtemplate C++ templateと.NET Genericsの違い
  128. 128. 型違いの処理 • 型だけ違う処理、コピペで書いていませんか 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() { … } }
  129. 129. ジェネリック† • 型をパラメーター化 † 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() { … } }
  130. 130. 型の直行化 • 要素の型と、処理・要素管理は分けましょう 要素の型 処理 int byte double string … Max Min Average … 管理方式 List LinkedList Stack …
  131. 131. 型の直行化 • 要素の型と、処理・要素管理は分けましょう 要素の型 処理 int byte double string … Max Min Average … 管理方式 List LinkedList Stack … Lパターン Mパターン Nパターン 分けて作らないと L×M×N 通りのパターン 分けて作ると L+M+N 通り
  132. 132. 2.0からなので… • 1.0の頃の名残がちらほら… • 今となっては GroupCollection MatchCollection AttributeCollection StringCollection … IEnumerable<Group> IEnumerable<Match> IEnumerable<Attribute> IEnumerable<string> … IReadOnlyCollection<Group> IReadOnlyCollection<Match> IReadOnlyCollection<Attribute> IReadOnlyCollection<string> … もしくは 1つの型で済む † † System.dllに実在する型。ごくごく一部の抜粋。
  133. 133. .NETのジェネリック • ILレベル対応 • メタデータがちゃんと残る • リフレクションで厳密な型をとれる・区別できる • ジェネリック用命令持ってる • JIT時に展開 • キャストとかの不要コードが消える • 値型のボックス化も消える • 値型の展開はかなりパフォーマンスに寄与
  134. 134. おまけ: C++のtemplate • コンパイル時に展開 • 超高機能なマクロみたいなもの • ○ 実行時のパフォーマンスいい • ○ かなり自由が効く • × 実行可能ファイル サイズが肥大化しがち • × コンパイル エラーが出た時結構悲惨
  135. 135. おまけ: Javaのジェネリック • 型消去 • コンパイル時に実は型が消えてる • 全部object扱い • ○? Java 1.0の頃からbytecode命令増えてない • × 実行時にリフレクションで情報とれない • ×キャストが挟まる • × 全然違う型にキャストできちゃう(実行時例外)
  136. 136. いろいろ窮屈な面も • .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
  137. 137. いろいろ窮屈な面も • 特に困るのが演算子使えないこと • 実体は静的メソッドなので static T Sum<T>(T[] array) { T sum = default(T); foreach (var x in array) sum += x; return sum; } コンパイル エラー 演算子定義されてない
  138. 138. いろいろ窮屈な面も • dynamicでごまかせなくはないけども… • パフォーマンス出ないので最後の手段 static T Sum<T>(T[] array) { dynamic sum = default(T); foreach (var x in array) sum += x; return sum; } 実行時コード生成で +演算子が呼ばれる
  139. 139. ポイント • 型のパラメーター化 • 似て非なるコピペ コードの解消 • 型の直行化 • {int, byte, string, …} × {List, Stack, Queue, …} • .NETのジェネリックはILレベル対応 • メタデータあり • 実行時にリフレクションで情報とれる • JIT時に展開 • キャストなどの不要なコードは挟まらない
  140. 140. イテレーター C# 2.0 イテレーター生成用の構文
  141. 141. データの列挙 • プログラム中、データの列挙は非常に多い • データを使う側はすごく楽 • 作る側、加工する側は? foreach (var x in data) { Console.WriteLine(x); }
  142. 142. データの列挙の例 • 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を使いたいたびに同じコード書くの?
  143. 143. データの列挙の例 • 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)って言う 作るの結構面倒
  144. 144. イテレーターの例 • 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を持つ関数ブロック) イテレーター クラスを自動生成
  145. 145. 内部実装(全体像) • クラス生成 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); }
  146. 146. 内部実装(ローカル変数) • ローカル変数 → フィールド class SubstringEnumerable : IEnumera { readonly string _s; int _len; int _i; int _state = 0; public SubstringEnumerable(strin public string Current { get; pri public bool MoveNext() { if (_state == 1) goto STATE1 if (_state == -1) goto END; 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); }
  147. 147. 内部実装(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)) go _state = 1; Current = _s.Substring(_i, _len); return true; STATE1: ; _i++; goto LOOP2BEGIN; 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); }
  148. 148. 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で囲う
  149. 149. 内部実装(中断と再開) • yield returnの部分、意味合いとしては • 中断と再開 yield return x; _state = 1; Current = x; return true; case 1:; 状態の記録 現在の値の保持 復帰用のラベル
  150. 150. おまけ: 他の言語だと • 最近の言語は結構似た機能†持ってる • スタック丸ごとキャプチャしてしまうもの • 中断時に記録、再開時に復元 • 式を継続渡しスタイル(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;
  151. 151. おまけ: 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; } }
  152. 152. ちなみに • コーディング面接で有名な某社の社員さん曰く、 • 例に使ったのsubstring列挙も割とよく出てくるパ ターン • 中断と再開って大事 アルゴリズムの問題はかなりの割合、 イテレーターを使うとあっさり書ける 「 」
  153. 153. ポイント • データ列挙は多い • データを作る側と使う側をきっちり分離しよう と思うと結構面倒 • 特に、作る側 • 作る側を簡単化する仕組みがイテレーター 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); }
  154. 154. LINQ (Language Integrated Query) C# 3.0 データ処理の直行化
  155. 155. 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メソッド が呼ばれるだけ 条件を満たす要素だけ残す 要素ごとに処理をかける
  156. 156. データ処理の直行化 • データの入力、加工、出力は分けましょう ユーザー入力から Console.Read… 配列から { 1, 2, 3, … } データベースから SELECT x FROM t ファイルから File.Read… 1, 2, 3 変換 x => x * x 1, 4, 9 1, 2, 3 選択 x => x < 3 1, 2 1, 2, 3 グループ化 x => x % 2 {1, 3}, {2} コンソールに Console.Write… 配列に ToArray() データベースに INSERT INTO t ファイルに File. Write… 入力 加工 出力
  157. 157. データ処理の直行化 • 掛け算を足し算に ユーザー入力から Console.Read… 配列から { 1, 2, 3, … } データベースから SELECT x FROM t ファイルから File.Read… 1, 2, 3 変換 x => x * x 1, 4, 9 1, 2, 3 選択 x => x < 3 1, 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 通り
  158. 158. 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があれば実装簡単
  159. 159. C# 3.0 • ラムダ式 • 匿名型 • 拡張メソッド
  160. 160. ラムダ式 • 匿名関数を簡単に書ける • =>演算子 • goes to (~になる)演算子 • 左が引数で、右が関数本体 • 型推論も効いてる source.Select(x => x * x); (シーケンスの全要素を二乗)
  161. 161. そもそも: 匿名関数 • メソッドの自動生成 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; }
  162. 162. ローカル変数のキャプチャ • 実はクラス生成 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; } } ローカル変数が フィールドに昇格
  163. 163. 匿名型 • 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; } }
  164. 164. 拡張メソッド • 静的メソッドを後置き記法で書ける • 単に語順を変えるだけ source.Select(x => x * x); System.Linq.Enumerable.Select( source, x => x * x); 同じ意味
  165. 165. 語順を変えるだけで • 語順のインパクト意外と大きい 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)); 逆順 処理順 ( ) が遠い ( ) が近い 普通の静的メソッド 拡張メソッド
  166. 166. 静的メソッド • “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); } 静的メソッド 無害なものにしか触れない限り、無害
  167. 167. 静的メソッド • “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) 書き換えが起こらないメソッド = 同じ引数を与えたら常に同じ結果しか返らない = テストしやすくていい
  168. 168. ポイント • C# 3.0 • ラムダ式 • 匿名型 • 拡張メソッド • 小さくて、汎用的な機能の集まり • LINQ以外でも有用 LINQ (データ処理用構文・ライ ブラリ)に関連して入った機能
  169. 169. dynamic型 C# 4.0 ダック タイピング用の型
  170. 170. おさらい: メタデータ • プロパティ名とかは、本来、実行時に要らない IL_0000: ldarg.0 IL_0001: ldfld int32 T::X IL_0006: ldarg.0 IL_0007: ldfld int32 T::Y IL_000c: mul IL_000d: ldarg.0 IL_000e: ldfld int32 T::Z IL_0013: mul IL_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の状態 実行時(ネイティブ化した状態) 名前が残ってる 名前が消えて、 レイアウト情報 だけになってる
  171. 171. ダック タイピング† • 同じ名前のメンバーを持っていれば 同じ型扱いできないか † アヒルのように歩き、アヒルのように鳴くなら、それはアヒルだ」 という論法からきた比喩表現 class Point { public int X { get; set; } public int Y { get; set; } public void Add(Point p) { X += p.X; Y += p.Y; } }
  172. 172. ダック タイピング† • 同じ名前のメンバーを持っていれば 同じ型扱いできないか † アヒルのように歩き、アヒルのように鳴くなら、それはアヒルだ」 という論法からきた比喩表現 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というプロパティ (またはフィールド)を 持っている任意の型を 使いたい こういう処理にはリフレクション (メタデータの実行時利用)が必要
  173. 173. 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というプロパティ (またはフィールド)を 持っている任意の型を 使える もちろん、内部的には リフレクション使ってる
  174. 174. dynamic型 • 結構用途が限られる • そもそもC#でダック タイピングしたい状況が稀 • ほぼ、外部との連携用 • COMとの連携 • 動的言語との連携 • JSONみたいなスキーマレスなデータ読み書き • その他、例えばできること • 多重ディスパッチ • できないこと • メタプログラミング • C#のスクリプト的実行
  175. 175. 例: 動的言語との連携 • 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コード
  176. 176. 例: 多重ディスパッチ • 静的な型に対して class Base { } class A : Base { } class B : Base { }
  177. 177. 例:多重ディスパッチ • 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"; } } 動的な呼び出し
  178. 178. 例:多重ディスパッチ • 呼び出し例 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)); }
  179. 179. dynamic型の内部実装 • コード生成結果 dynamic X(dynamic x) { return x.X; } object X(object x) { if (_site1 == null) { _site1 = CallSite<Func<CallSite, o Binder.GetMember(CSharpBinderF new CSharpArgumentInfo[] { CSharpArgumentInfo.Create( })); } return _site1.Target(_site1, x); } 実際にはobject 動的コード生成 用の情報 これが本体
  180. 180. CallSite.Targetの中身 • メソッドを動的コード生成してる • 生成したメソッドはキャッシュして持っておく (inline method cache) static object _anonymous(CallSite site, object x) { return site.Targetを更新する処理 } _site1.Targetの初期状態
  181. 181. 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行追加 単なる型判定+キャスト
  182. 182. 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 で高速化してる
  183. 183. “heavy” dynamic • 用途によっては高性能なものの… • 多くの場合、過剰スペック • できるけど過剰スペックな例 • JSONみたいなスキーマレスなデータ読み書き • もっと”light-weight”なdynamicが必要 p.X; p.M(); p.Get("X"); p.Invoke("M"); 例: 規約ベースの置き換え 一時期、「p.$x を p["x"] と解釈しよう」 という提案はあったけども、結局立ち消え中
  184. 184. できないこと • メンバー名がコンパイル時に既知でないとダメ • なのでメタプログラミングには使えない • そのためにはもっと低レイヤーなAPI使う • IL Emit • Expression Tree • Roslyn dynamic x = p; dynamic y = q; y.X = x.X; y.Y = x.Y; コンパイル時に既知
  185. 185. ポイント • dynamic型 • C#でダック タイピング • 主に外部との連携用 • inline method cache • 用途があえば結構高性能 • 用途によっては過剰スペック • メタプログラミング用ではない
  186. 186. async/await C# 5.0 非同期処理
  187. 187. スレッド関連おさらい • 非同期処理 • マルチタスク • UIスレッドを止めない • I/O (外の世界との入出力待ち) • Taskクラスを使いましょう • スレッド プール • I/O完了ポート 割と避けれないこと なんだけども…
  188. 188. 非同期処理しんどい • 普通に非同期処理やったらコールバック地獄 • 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回だから この程度で済んでる
  189. 189. • 複数の確認ダイアログ表示 面倒な非同期処理の例 確認 1 チェック 確認 2 チェック 確認 3 チェック No No Yes Yes Yes 確認フロー 結果表示 No ゲームで アイテムを 合成します レア アイテムですよ? 合成強化済みですよ? もう強化限界ですよ? ユーザーから の入力待ちも 非同期処理
  190. 190. 同期処理 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;
  191. 191. 非同期処理 (旧) • 画面に収まるように フォント サイズ調整 • 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);
  192. 192. 非同期処理(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演算子が増えただけ • ダイアログの数が増えても平気
  193. 193. イテレーターと似た仕組み • イテレーター(ジェネレーター)があれば、割と 単純なラッパーで非同期処理も可能 • 要は、「中断と再開」 • 例えば、TypeScriptでは • 現状でもawaitのMS社内実装は持ってる • いまだと、生成されるJavaScriptが悲惨すぎて大変 • EcmaScriptにジェネレーターが実装されてから、 TypeScriptにawaitを追加する予定
  194. 194. 参考: 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: 状態の記録 中断 再開用のラベル
  195. 195. 基本的な考え方 • 概念としては イテレーター+継続呼び出し 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; 中断 状態の記録 結果の受け取り 再開用のラベル 非同期処理が終 わったら続きから 呼び出してもらう
  196. 196. 実際の展開結果 • 実際はもう少し複雑 • 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
  197. 197. Awaitable • Awaitableなクラスは自作可能 • 実装次第でいろいろ • Windowd Runtime • IAsyncOperationインターフェイスとかをawaitできる • TaskのAwaitable実装 • UIスレッドへのディスパッチを内部的にやってくれる • そうしたくない場合の実装もある • ConfigureAwait
  198. 198. おまけ: 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; }
  199. 199. おまけ: immutable • 並列処理といえばimmutableだけども • 書き換えが起こらないなら複数のスレッドで共有し ても安全 • この用途だと、C++のconstは不十分 • あれは参照渡しを安全に行う用であって • 呼び出し元の側ではconstとは限らず、書き換わる 可能性あり • あと、mutable修飾子を付ければconstなオブジェク トすら書き換えれる
  200. 200. ポイント • 非同期処理は、避けれないんだけども面倒 • async/awaitで多少マシに • 実装的にはイテレーターと同様、中断と再開
  201. 201. .NET Compiler Platform Compiler as a Service コンパイラーの内部データを活用
  202. 202. .NET Compiler Platform† • C#/VBコンパイラーを再設計・再実装 • .NET実装 • C#実装のC#コンパイラー • VB実装のVBコンパイラー • コンパイラーの内部データを誰でも自由に使える † コードネーム“Roslyn”って呼ばれてたやつの正式名称 コンパイラーのサービス化、 プラットフォーム化
  203. 203. 用途 • C#スクリプティング • C#で設定ファイル書きたい(むしろXMLがいや) • アプリの再起動なしでロジック更新 • ソースコード配置 • .NET vNext, Cloud Mode • コード解析 • コードのインデックス化† • サーバー ビルド、テスト • IDE連携 現状の最優先事項 † .NETの参照ソースコードが実際やってる http://referencesource.microsoft.com/
  204. 204. 構文ハイライト • 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(); } メソッド名 クラス名 変数 キーワード
  205. 205. リアルタイム エラー検出 • エラー検出タイミングがビルド時とか遅い • Visual Studioは常時やってる
  206. 206. コード補完 • タイピングめんどくさい • 文法やライブラリ、いちいち覚えたくない
  207. 207. リファクタリング • 最初からきれいなコード書くのめんどくさい • でもほっときたくない
  208. 208. ということで • 今、コンパイラーに求められる要件 • ソースコードのどこからどこまで(何行何列目)が 何かという情報がとれる • 文脈に応じてソースコードを生成したり、書き替え たりできる • リアルタイム処理を必要とするので、パフォーマン スも求められる
  209. 209. 実はこれまで • コンパイラー(パーサー)を2重開発してた • コンパイル用 • IDE用 • まして、サード パーティ製コード解析プラグイン も含めると、3重開発 • Java (EclipseとかIntelliJとか)でも • (去年ぐらいによく言われてたけども) 「Eclipseが対応するまでJava 8プレビュー試せない や」
  210. 210. おまけ: 他の環境(Clang) • Clangはその辺りをゴールの1つに掲げてる • IDEで使う前提 • リファクタリングに使いやすいデータ構造の構文木 • インクリメンタル コンパイルとかもしやすい
  211. 211. IDE連携まで考えると • 簡単そうに見える文法ですら、実装コストかな り高い • 2重開発はそれだけ大変 • だからIDEを信用しない/できない人も多い • IDE対応待ってられない/待ちたくない • .NET Compiler Platformはそれを解消 • C#に新機能を足しやすくなる • C# 6.0
  212. 212. 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な型を作りやすく 「式」で書けることの幅が広がる
  213. 213. ポイント • 実行可能ファイルを作るだけがコンパイラーの 仕事じゃない • スクリプト実行 • コード解析 • 特に、IDE連携 • しかも、リアルタイム処理 • C#/VBコンパイラーの再設計 = .NET Compiler Platform このあたりまで考えると ちょっとした言語機能を 足すのも大変

×