Successfully reported this slideshow.
Your SlideShare is downloading. ×

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

Ad
Ad
Ad
Ad
Ad
Ad
Ad
Ad
Ad
Ad
Ad
Upcoming SlideShare
C#とILとネイティブと
C#とILとネイティブと
Loading in …3
×

Check these out next

1 of 167 Ad

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

Download to read offline

2014/3/1 Boost勉強会 #14 東京 にて
https://sites.google.com/site/boostjp/study_meeting/study14

Boost勉強会なのに.NETの話で、1人だけ1時間(他の人は30分)。
本来、自分のペースでは4時間くらいかかってもおかしくない分量を1時間で。

2014/3/1 Boost勉強会 #14 東京 にて
https://sites.google.com/site/boostjp/study_meeting/study14

Boost勉強会なのに.NETの話で、1人だけ1時間(他の人は30分)。
本来、自分のペースでは4時間くらいかかってもおかしくない分量を1時間で。

Advertisement
Advertisement

More Related Content

Slideshows for you (20)

Similar to C#や.NET Frameworkがやっていること (20)

Advertisement

More from 信之 岩永 (20)

Recently uploaded (20)

Advertisement

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

  1. 1. C#や.NET Framework がやっていること 岩永 信之
  2. 2. 自己紹介 C#でぐぐれ こんなロゴのサイトの中の人 プログラミングの魔導書 Vol.3にも何か書いてるよ
  3. 3. 今日の内容 • C#や.NET Frameworkがやっていること • どうしてそういう機能がある • (自前で実装するなら)どうやればできる • (自前でやる上で) 気を付ける必要がある点
  4. 4. 趣旨 • 言語的に機能がないからって、やらなくていい わけじゃない。 • C#とかが持ってる機能は必要だから追加されてる • 自前で同様のことをやる方法も知ってて損ない • 例えば… • C言語でもOOPやってる人はやってた • C言語でも仮想関数テーブル自前で書いたり
  5. 5. 注意 170ページ • 本来、1時間で話す内容じゃない • 置いてきぼる気満々 • あんまり細かい説明しない(というかできない)
  6. 6. まずは .NET Frameworkといえば “Managed”
  7. 7. “Managed” • .NET Frameworkの管理下にある • メモリ管理 • メモリ解放 • 境界チェック • ドメイン分離 • 型の管理 • メタデータ • JIT • PCL
  8. 8. Garbage Collection メモリ リークをなくすために
  9. 9. .NET FrameworkのGC • .NET FrameworkのGCはMark & Sweep • 世代別 (3世代、Gen0~2) • Compactionあり • Backgroundスレッドで並列実行
  10. 10. Mark & Sweep • ルート†から参照をたどる ルート ヒープ Mark 使用中オブジェクト Sweep 未使用オブジェクト †スタックや静的フィールド中のオブジェクト
  11. 11. Compaction • GC時にオブジェクトの位置を移動 隙間をなくすように移動 後ろが丸ごと空くので次のメモリ確保が楽 使用中オブジェクト 未使用オブジェクト
  12. 12. Generation • GC時にオブジェクトの位置を移動 オブジェクトの寿命 統計的に • 短いものはとことん短く • 長いものはとことん長い Gen0 Gen1 しばらくここは ノータッチ Gen0 この範囲でだけ メモリ確保・GC 一度GCかかったオブジェクト はしばらく放置
  13. 13. C++でのGC • 通常、参照カウント • shared_ptr • 循環参照に気を付けて適宜weak_ptrを • Mark & Sweep GCライブラリもあるけども • 型がはっきりしないので保守的にならざるを得ない • すべての数値をポインターとみなしてMark
  14. 14. Mark & Sweepと参照カウント • 比較 • 一長一短ある Mark & Sweep 参照カウント メモリ確保 ○ 末尾を見るだけ × 空いている場所を探す × カウント用の領域が追加 で必要 変数のコピー ○ ポインターのコピー × ポインターのコピーに加 え、参照数のカウント アップ メモリ解放 × MarkやCompactionに 時間がかかる × 負担が1か所に集中 ○ カウントが0になった時 にdeleteするだけ
  15. 15. Throughput • トータルの性能(throughput)はMark & Sweep の方がいい Mark & Sweep メモリ確保 参照カウント ○ 末尾を見るだけ × 空いている場所を探す × カウント用の領域が追加 で必要 ○ ポインターのコピー 変数のコピー 参照局所性も高くなって キャッシュが効きやすい メモリ解放 × MarkやCompactionに 時間がかかる × 負担が1か所に集中 まとめてやる方がバラバラに やるより効率がいい × ポインターのコピーに加 え、参照数のカウント アップ 頻度が高い操作なので ○ カウントが0になった時 ここの負担が大きいと にdeleteするだけ 全体の性能落ちる 特に、スレッド安全を求めるときつい たかがインクリメントでも、atomic性 保証するとそこそこの負担
  16. 16. 短所の軽減 • 短所も、まったく打つ手がないわけじゃない Mark & Sweep 参照カウント メモリ確保 ○ 末尾を見るだけ × 空いている場所を探す × カウント用の領域が追加 で必要 変数のコピー ○ ポインターのコピー × ポインターのコピーに加 え、参照数のカウント アップ メモリ解放 .NET 4以降、Background スレッド実行してる × MarkやCompactionに 時間がかかる × 負担が1か所に集中 ○ カウントが0になった時 C++ 11的には にdeleteするだけ 「move semantics活用 してね」
  17. 17. 自前メモリ管理との混在 • スコープが短いならスタックにとればいいのに • C#はclassは必ずヒープ、structはスタックになる • 使い分けれない • Mark & Sweep管理領域内のポインターを管理 外に渡すときには注意が必要 • Compactionでメモリ移動しないように 「ピン止め」が必要 • 結構ガベージ コレクションのパフォーマンス落とす
  18. 18. おまけ • Pythonとか • 参照カウントとMark & Sweepの併用 • ○ 循環参照問題避けつつ、負担を1か所に集中させない • × 性能的には悪いとこどり • Go • 割当先を自動判別 • スコープ内で完結していたらスタックに • そうでなければヒープに • これはこれでスタックの浪費激しそうなんだけども… • なので、可変長スタック持ってる
  19. 19. 境界チェック 意図しないメモリ領域にはアクセスさせない
  20. 20. 配列の境界チェック • .NETの配列は厳しい • 常に境界チェックしてる 配列 a a[-1] OutOfRange a[length+1] OutOfRange • 要はbuffer overrun防止 • ちなみにJITの最適化で、明らかに境界を侵さない ものは境界チェックを消す
  21. 21. unsafe • C#にもポインターあるんだけども 安全でないコード この中でだけポイ ンター利用可能 var x = new[] { 1, 2, 3, 4, 5 }; unsafe { fixed(int* px = &x[0]) メモリ移動の防止 { (ピン止め) Console.WriteLine(px[100]); } } 範囲チェックなし buffer overrunやりたい放題
  22. 22. unsafeでも制限付き • class (参照型)はアドレス取れない • 仮想メソッド テーブル(vtable)とか入ってるし • 親クラス側のレイアウト変更の影響受けるし class Class var c { fixed public int x; fixed public int y; fixed } ピン止め必須 • • = new Class(); (void* p = &c) { } // error (void* p = &c.x) { } // OK (void* p = &c.y) { } // OK 値型のメンバーのアドレスは取れる オブジェクト自体のアドレスは取れ ない
  23. 23. unsafeでも制限付き • struct (値型)はアドレス取れる • ただし、メンバーが全部値型の時のみ • ↑「unmanaged型」と呼ぶ struct UnmanagedStruct { public int x; public int y; } メンバーが 全部値型 var u void* void* void* = new UnmanagedStruct(); p1 = &u; // OK p2 = &u.x; // OK p3 = &u.y; // OK 無制限にアドレス取れる
  24. 24. unsafeでも制限付き • struct (値型)であっても制限付き • メンバーに1つでも参照型を含むとダメ • ↑「managed型」と呼ぶ struct ManagedStruct { public int x; public string y; } メンバーに 参照型が1つ var u void* void* void* = new ManagedStruct(); p1 = &u; // error p2 = &u.x; // OK p3 = &u.y; // error 値型のメンバーのところ だけはアドレス取れる
  25. 25. ポイント • unsafe • 危険なことは基本認めない • 面倒な追加の構文を要求 • コンパイル オプションでも/unsafeの明記必須 • fixed • ガベージ コレクションとの兼ね合い • Compaction阻害になるので注意 • 「Unmanaged型」に限る • 実装依存な部分(vtableとか)や、 型の定義側の変更が利用側に極力影響しないように
  26. 26. AppDomain 実行環境の分離 セキュリティ保証
  27. 27. コンポーネントの連携 • 他のアプリの機能を自分のアプリから呼びたい • WordとかExcelとかのドキュメントを出力 • サーバー上に自分のアプリを配置したい • IIS上に(ASP.NET) • SQL Server上に • (必ずしも)信用できない • セキュリティ保証 • 参照先のクラッシュにアプリ/サーバー が巻き込まれないように • コンポーネントのバージョン アップ
  28. 28. AppDomain • 単一プロセス内で、分離された複数の実行領域 (domain)を提供 プロセス AppDomain 1 AppDomain 2 … 必ずしも信用できないコードを 安全に、動的に呼び出し • 「分離」 • plugin: 動的なロード/アンロード • security: AppDomainごとに異なる権限付与 • isolation: 別メモリ空間
  29. 29. ドメイン間には壁がある • 別メモリ空間に分離されてる AppDomain 1 AppDomain 2 互いに独立
  30. 30. ドメイン間の通信 • マーシャリング(marshaling) • marshal (司令官、案内係)の命令通りにしか壁を超 えれない • 司令がダメといったら通れない • 司令の指示通りの形式にいったん シリアライズしないといけない AppDomain 1 AppDomain 2
  31. 31. ドメイン間の通信 • ダメな例 あるオブジェクト AppDomain 1 これをBに渡し たいとして AppDomain 2
  32. 32. ドメイン間の通信 • ダメな例 AppDomain 1 AppDomain 2 shallow copyなんてしようもんなら A側のメモリへの参照が残る
  33. 33. ドメイン間の通信 • よい例 一度シリアライズ 必ずdeep copy { {1, 2}, {3, 4}, {5, 6} serialize AppDomain 1 } deserialize AppDomain 2 この辺りがマーシャリング
  34. 34. .NETのマーシャリング • 2種類 • marshal by ref • 参照を渡すんだけど、メモリ領域は直接触れない • メソッド越しにしか操作しちゃいけない • 「このメソッドを呼んでくれ」っていうメッセージだけ がドメインを超えてわたって、実際の実行は相手側ドメ インで • marshal by value • 値をシリアライズして相手側に渡す • 規定では、BinarySeralizerを使用 privateフィールドまで含めてリフレクションで 内部状態を取得してシリアライズ
  35. 35. .NETのマーシャリング(文字列) • marshal by valueの場合でも文字列は特殊扱い • コピーしない • 参照をそのまま渡す • immutableかつrange-check付きに作ってある • COM (ネイティブ)に対して文字列を渡す時すらこの方式 (ただし、文字コード変換が不要な場合だけ)
  36. 36. その他のコスト (マーシャリングのコスト以外にも) • いくらかコストが • 例えばライブラリの読み込み Library X AppDomain 1 Library X AppDomain 2 別ドメインに全く同じライブラリを読み込んでも、 それぞれ別イメージが作られる Global Assembly Cache (GAC)にあるDLLだけは別 GAC中のは同じメモリ イメージが共有される
  37. 37. C++でも • COMなんかはマーシャリングやってる • 逆にいうと、COMくらい仰々しいものが必要 • さもなくば、プロセス分離してプロセス間通信とか やらなきゃいけない • 安全だけどかなり高負荷 • RPCとか一時期流行ったけども • ただの関数呼び出しに見えるコードで、内部的にプロセス 間通信するとか
  38. 38. メタデータ 型情報 動的リンクでのバージョン管理
  39. 39. メタデータ • メタデータとは • DLLにどういう型が含まれるかという情報 • 外部のどういうDLLを参照しているかの情報 • バージョン情報 • メタデータがあると • プログラミング言語をまたげる • 動的リンクでのバージョン管理
  40. 40. 動的リンク • ライブラリは共有する アプリA ライブラリX version 1 アプリB ライブラリY version 1
  41. 41. 動的リンク • ライブラリ単体での差し替え • セキュリティ ホールや、致命的なバグの修正 差し替え アプリA 更新不要 ライブラリX version 2 アプリB ライブラリY version 1
  42. 42. いまどきの事情 • ライブラリはアプリのパッケージに同梱 • バージョニングいるの? アプリAパッケージ アプリA アプリBパッケージ ライブラリX ライブラリY ライブラリX アプリB 別バイナリ ライブラリY
  43. 43. 差分ダウンロード ※ Windowsストア アプリはこの仕組み持ってるらしい アプリAパッケージ version 1 アプリA version 1 アプリAパッケージ version 2 ライブラリX version 1 アプリA version 1 ライブラリY version 1 ライブラリX version 2 ライブラリY version 1 差分 ライブラリX version 2 バージョンアップ時 ダウンロード アプリA ver.1 インストール機
  44. 44. アプリ間でライブラリ共有 ※ Windowsストア アプリはこの仕組み持ってるらしい アプリAパッケージ アプリA アプリBパッケージ ライブラリX version 1 ライブラリX version 1 アプリB ライブラリY version 1 ライブラリY version 1 差分 アプリB アプリBインストール時 X、Yは同じものを共有 (ハード リンク作るだけ) アプリA インストール機
  45. 45. メタデータの作成 • COMの頃 • メタデータを自分で書いてた (自動化するツール使あるけど) • .tlb/.olbファイル • .NET Framework • .NET自体ががメタデータの規格を持ってる • C#とかをコンパイルするだけで作られる
  46. 46. C++/CX • C++ Component Extensions • マイクロソフトのC++拡張 素の標準 C++コード C++/CX コンパイル C++内で完結して使う分 にはオーバーヘッドなし COMコード COMを呼べる言語なら 何からでも呼べる メタデータ (winmd) .NETのメタデータと互換 .NETから簡単に呼べる
  47. 47. JIT (Just-in-Time compile) バージョン アップ時の変更の影響を吸収
  48. 48. 中間コードとJITコンパイル • .NET Frameworkの中間言語 ビルド時に コンパイル 高級言語 (C#など) Just-in-Time コンパイル 中間言語 ネイティブ (IL) コード JITである必要ない LLVMとかでも中間言語介してコンパイルしてる • 高級言語のコンパイラーを作る人と、CPUごとの最 適化する人の分業化 ストア審査ではじくとか他にも手段はある • セキュリティ チェックしやすくなったり • 動的リンク時に、コード修正の影響をJITで吸収
  49. 49. 例 • (C#で)こんな型があったとして public struct Point { public int X; public int Y; public int Z; } • 整数のフィールドを3つ持つ
  50. 50. 例 • こんなメソッドを書いたとする static int GetVolume(Point p) { return p.X * p.Y * p.Z; } • フィールドの掛け算
  51. 51. 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 }
  52. 52. ネイティブ コード • JIT結果 (x64の場合) push mov cmp je call mov lea imul lea imul pop ret ebp ebp,esp dword ptr ds:[5011058h],0 00FE2A01 74B7AEA8 eax,dword ptr [ebp+8] edx,[ebp+8] eax,dword ptr [edx+4] edx,[ebp+8] eax,dword ptr [edx+8] ebp 0Ch 4とか8とかの数値に 型情報は残らない
  53. 53. メモリ レイアウト • この4とか8の意味 Point X public struct Point { public int X; public int Y; public int Z; } Y 4バイト 8バイト Z ※レイアウトがどうなるかは環境依存
  54. 54. メモリ レイアウト • この4とか8の意味 ILの時点までは名前 で参照してる public struct Point Point X { public int X; public int Y; public int Z; } Y 4バイト 8バイト ネイティブ コードは レイアウトを見て Z 数値で参照してる ※レイアウトがどうなるかは環境依存
  55. 55. 数値でのフィールド参照 • 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)
  56. 56. 変更してみる • 大して影響しなさそうな ほんの些細な変更をしてみる public struct Point { public int X; public int Y; public int Z; } public struct Point { public int X; public int Z; public int Y; } フィールドの順序変更
  57. 57. その結果起きること • メモリ レイアウトが変わる※ Point Point X X Y Z Z Y ※ この例(フィールド変更)以外でも、仮想メソッド とかいろいろレイアウトが変わるものがある テーブル
  58. 58. ILレベルでの影響 • 影響なし IL_0000: IL_0001: IL_0006: IL_0007: IL_000c: IL_000d: IL_000e: IL_0013: IL_0014: ldarg.0 ldfld ldarg.0 ldfld mul ldarg.0 ldfld mul ret int32 Point::X int32 Point::Y int32 名前で参照してるん だから特に影響ない Point::Z JITが吸収してくれる
  59. 59. ネイティブ レベルでの影響 • ここで影響が出る push mov cmp je call mov lea imul lea imul pop ret ebp ebp,esp dword ptr ds:[5011058h],0 00FE2A01 74B7AEA8 eax,dword ptr [ebp+8] edx,[ebp+8] eax,dword ptr [edx+4] 8 edx,[ebp+8] 更新が必要 4 eax,dword ptr [edx+8] ebp 0Ch 利用側の再コンパイルが必要 ライブラリ側だけの差し替えじゃダメ
  60. 60. ただし… • この役割に焦点を当てるなら… • 毎回毎回JITする必要ない • 全部が全部ILな必要ない
  61. 61. Ngen • Ngen.exe • Native Image Generator • ILを事前にネイティブ化するためのツール • 自前管理が必要 • アプリのインストーラー※とかを作って明示的に呼び出し • 参照しているライブラリが更新された時には呼びなおす 必要あり • かなり面倒なのでアプリを Ngenすることはめったにない • .NET自体が標準ライブラリの 高速化のために使ってる ※ 要するに、JITの負担を起動時じゃなくてインストール時に前倒しする
  62. 62. Auto-Ngen • .NET Framework 4.5以降なら • NgenがWindowsサービスとして常に動いてる • アイドル時に動作 • 利用頻度の高いものを自動的にNgen • デスクトップ アプリの場合はGACアセンブリのみ • Windowsストア アプリの場合はすべてのアセンブリ • よく使うアプリの起動はだいぶ早くなる • インストール直後の起動は相変わらず遅い
  63. 63. MDIL (ネイティブのおさらい) • おさらい: ネイティブ コードだと push mov cmp je call mov lea imul lea imul pop ret ebp ebp,esp dword ptr ds:[5011058h],0 00FE2A01 参照しているライブラリ 74B7AEA8 eax,dword ptr [ebp+8] のレイアウトが変わった edx,[ebp+8] 時に再コンパイルが必要 eax,dword ptr [edx+4] edx,[ebp+8] eax,dword ptr [edx+8] ebp 0Ch
  64. 64. MDIL (部分的にネイティブ化) • じゃあ、こんな形式があればいいんじゃ? push mov cmp je call mov lea imul lea imul pop ret ほぼネイティブ ebp レイアウトのところ ebp,esp dword ptr ds:[5011058h],0 だけ抽象的に型情報 00FE2A01 を残しておく 74B7AEA8 eax,dword ptr [ebp+8]Point::X int32 edx,[ebp+8] eax,dword ptr [edx+4]Point::Y int32 edx,[ebp+8] eax,dword ptr [edx+8]Point::Z int32 ebp 0Ch
  65. 65. MDIL (Compile in the Cloud) • 実際、Windows Phoneのストアでは そういう形式が使われている: MDIL (Machine Dependent Intermediate Language) C#コード C#コンパイラー 開発環境でILにコンパイル MDILコンパイラー Windowsストア サーバー 上でILをMDIL化 (Compile in the Cloud) IL MDIL リンカー ネイティブ コード Windows Phone実機上では レイアウトの解決(リンク)だけ行う • インストール直後の起動も安心
  66. 66. C++だと • 動的にやろうと思ったら、結局、COMとか WinRTになってく • いったんQueryInterfaceみたいなの挟む • 結構でっかいオーバーヘッド† † WinRTはオーバーヘッド解消の仕組みも持ってて、 C++で完結してる限りには普通の仮想関数呼び出しになる 他言語から使う場合にはCOMアクセスになる
  67. 67. C++だと • 静的な話で言うと ヘッダーファイルを変更したら 利用側の再コンパイルが必要 Point.h struct Point { double x; double y; }; struct Point 要 再コンパイル { double radius; double theta; };
  68. 68. なので • setter/getter使う • pimpl使う Point.cpp (実装その1) struct PointImpl; struct PointImpl { double x; double y; }; class Point { public: double get_x(); double get_y(); private: PointImpl* impl; }; double Point::get_x() { return impl->x; } double Point::get_y() { return impl->y; } Point.h
  69. 69. なので Point.cpp (実装その2) こっちだけの変更なら、 setter/getter使う 利用側の再コンパイル不要 • • pimpl使う Point.h 変更なし struct PointImpl; class Point { public: double get_x(); double get_y(); private: PointImpl* impl; }; 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); }
  70. 70. Portable Class Library 複数の「標準ライブラリ」 いろんな実行環境の共通部分
  71. 71. 標準ライブラリ • マイクロソフト製品だけでも… デスクトップ クライアント アプリ この共通部分だけを 「標準」にすべき? 共通部分 サーバー アプリ Phone/タブレット クライアント アプリ
  72. 72. 標準ライブラリ • まして今、Xamarin (Mono) • Xamarin.iOS、Xamarin.Android Windows タブレット/Phone Windows デスクトップ Windows サーバー iOS Android この共通部分だけを 「標準」にすべき? Linux サーバー
  73. 73. .NET FrameworkとMono コンパイラー .NET Framework 実行環境 C# VB CLR F# ライブラリ .NET Full プロファイル .NET Core プロファイル .NET Full (サブセット) Mono C# Mono ランタイム iOS向け プロファイル Android向け プロファイル 実行環境に互換性 があっても 標準で使える ライブラリが違う
  74. 74. どこでも使えそうに見えても… • 例えばファイル システム • 最近のWindowsはいろんなプロパティを持ってる • 画像ファイルなんかだと: サムネイル画像、撮影日時、画像サイズ、タグ • 検索インデックスも張ってる こういうものも標準に含めたいか? • ストア アプリだとファイル システムすら制限下 • ユーザーの許可がないファイルには触れない 一番制限がきつい環境に合わせて標準ライブラリを作るべき?
  75. 75. 汎用 VS 特定環境 どこでも動く 特定環境で動く • 最大公約数 • 動作保証に時間が かかる • 高機能 • 早く提供できる
  76. 76. 汎用 VS 特定環境 どこでも動く • 最大公約数 • 動作保証に時間が かかる C++だと • 標準化にかかってる時間 特定環境で動く • ライブラリのソースコードをとってきたは • 高機能 いいけど自分の環境での動作確認も大変 • 早く提供できる
  77. 77. Portable Class Library • 実行環境ごとに 別の「標準ライブラリ」 メタデータを用意 • ライブラリ側でどの 実行環境をターゲットに するか選ぶ 実行環境ごとに 別メタデータを提供 ターゲット選択
  78. 78. Portable Class Library • 例: 2環境 共通部分
  79. 79. Portable Class Library • 例: 3環境 共通部分
  80. 80. C++ • ターゲット広すぎてより一層大変だけど • メタデータ持ってない言語でやるのも厳しい • #ifdef?
  81. 81. さて もうすでに約80ページ
  82. 82. ここまで/ここから • ここまで: .NETのインフラ的なところ • いわゆる“Managed” • C# 1.0の頃からある基本機能 • 所詮、既存言語をManagedにした程度 • ここから: C# 2.0以降の追加機能
  83. 83. C#の歴史 C# 4.0 C# 3.0 C# 2.0 C# 1.0 • Dynamic C# 5.0 • Async • WinRT • LINQ • Generics • Managed ※VB 7~11の歴史でもある
  84. 84. ジェネリック C# 2.0 C++で言うところのtemplate C++ templateと.NET Genericsの違い
  85. 85. C++ templateとC# generics C++ template C# generics 高機能マクロみたいなもので、コン パイル時に全部展開される .NETの中間言語/型情報レベルで対応 JIT時に展開 vector<T>とvector<U>で別コード が生成される List<T>とList<U>で同じコードを 共有(参照型の場合) すべてヘッダーに書く必要がある 変更すると利用側も再コンパイル 内部を変更しても(publicなところが 変わらない限り)利用側に影響なし コンパイル時に すべてやる方針 メタデータ/JITで話した要件 • 言語をまたげる • 動的リンク • バージョン管理 • 利用側に影響与えない .NET的にはこれを守るのが最重要
  86. 86. もしC++でgenericsやるなら (動的には無理だけど、せめて静的に) • いったん汎用クラスを作る class void_vector { public: void* operator[](int index); void add(void* item); void remove(void* item); … 後略 … いったんvoid*な 汎用クラスを作る
  87. 87. もしC++でgenericsやるなら (動的には無理だけど、せめて静的に) • キャストだけtemplateで template<typename T> class vector<T> { さっき作ったvoid*のクラス private: void_vector impl; キャストするだけの薄いtemplateクラスを作る 要素の型が保証されるのでstatic_castでOK 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); } };
  88. 88. もしC++でgenericsやるなら • このやり方(汎用クラス+templateでキャスト) なら • vector<T>とvector<U>でコード共有可能 • バイナリ サイズ小さくできる • (pimplを併用すれば)内部実装を変更しても、利用 側の再コンパイル不要 • コンパイル時間だいぶ短くできる
  89. 89. とはいえ、いろいろ窮屈 • 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
  90. 90. とはいえ、いろいろ窮屈 • 特に困るのが演算子使えないこと • 実体は静的メソッドなので static T Sum<T>(T[] array) { T sum = default(T); foreach (var x in array) sum += x; return sum; } コンパイル エラー 演算子定義されてない
  91. 91. とはいえ、いろいろ窮屈 • dynamicでごまかせなくはないけども… • 性能出ないので最後の手段 static T Sum<T>(T[] array) { dynamic sum = default(T); foreach (var x in array) sum += x; return sum; } 実行時コード生成で +演算子が呼ばれる
  92. 92. C++/CX • C++/CXはジェネリックを持ってる • genericキーワード • メタデータ(winmd)上は.NETのジェネリックと互換 • かなり制限きつい • 実装(genericなクラス)はpublicにできない • private/internalなクラスはtemplateで展開 • ユーザー定義のインターフェイス、デリゲートも genericな場合はpublicにできない
  93. 93. イテレーター C# 2.0 イテレーター生成用の構文
  94. 94. イテレーター ブロック • 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の中身)はめんどくさい これを楽にする
  95. 95. 例 • 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); } イテレーター ブロック† (= yield returnを持つ関数ブロック) 使う側 foreach (var x in GetSubstrings("abcd")) Console.WriteLine(x);
  96. 96. 内部実装(全体像) • クラス生成 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; } 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); } 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(); } }
  97. 97. 内部実装(ローカル変数) • ローカル変数 → フィールド 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 : 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;
  98. 98. 内部実装(yield return) • yield return → 状態記録、return、caseラベル • 中断と再開 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); } 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;
  99. 99. 内部実装(yield return) • yield returnの部分、意味合いとしては† switchで囲う 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 x; 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; } _state = 1; Current = x; return true; case 1:; † forループ内にラベル張れないからさっきみたいな複雑なコードになるけども
  100. 100. 内部実装(中断と再開) • yield returnの部分、意味合いとしては • 中断と再開 yield return x; 状態の記録 _state = 1; Current = x; return true; case 1:; 現在の値の保持 復帰用のラベル
  101. 101. 機械的な置き換えでできる • 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ラベルの制限が ゆるいので、むしろ簡単
  102. 102. 機械的置き換えといえば • マクロを定義 #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:;
  103. 103. 機械的置き換えといえば • マクロを使って 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 }
  104. 104. 他の言語だと • 最近の言語は結構似た機能持ってる • ジェネレーター(generator)って呼ばれることが多い • 実装方式: • スタック丸ごとキャプチャしてしまうもの • 中断時に記録、再開時に復元 • 式を継続渡しスタイル(CPS)に変換してしまうもの • スレッド使う • concurrent queue使って、yield returnのところでenqueue • さすがに性能的に論外だけども
  105. 105. ちなみに • コーディング面接で有名な某社の社員さん曰く、 「 アルゴリズムの問題はかなりの割合、 イテレーターを使うとあっさり書ける 」 • 例に使ったのsubstring列挙も割とよく出てくるパ ターン • 中断と再開って大事
  106. 106. 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; } }
  107. 107. LINQ (Language Integrated Query) C# 3.0 データ処理の直行化
  108. 108. 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メソッド が呼ばれるだけ
  109. 109. データ処理の直行化 • まあ、データの入力、加工、出力は分けましょ うという話 入力 加工 配列から { 1, 2, 3, … } 1, 2, 3 変換 x => x * x 1, 4, 9 データベースから SELECT x FROM t ファイルから File.Read… ユーザー入力から Console.Read… 1, 2, 3 選択 x => x < 3 1, 2 1, 2, 3 グループ化 x => x % 2 {1, 3}, {2} 出力 配列に ToArray() データベースに INSERT INTO t ファイルに File. Write… コンソールに Console.Write…
  110. 110. データ処理の直行化 • 掛け算を足し算に Lパターン Mパターン Nパターン 入力 加工 出力 配列から { 1, 2, 3, … } 1, 2, 3 変換 x => x * x 1, 4, 9 配列に ToArray() データベースから データベースに 1, 2, 3 選択 SELECT 分けて作らないと L×M×N 通りのパターン x FROM t INSERT INTO t x => x < 3 1, 2 分けて作ると L+M+N 通り ファイルから ファイルに File.Read… File. Write… 1, 2, 3 グループ化 x => x % 2 {1, 3}, ユーザー入力から コンソールに {2} Console.Read… Console.Write…
  111. 111. 関連するC#の言語機能 • C# 3.0 • ラムダ式 • 匿名型 • 拡張メソッド
  112. 112. ラムダ式 • 匿名関数を簡単に書ける source.Select(x => x * x); (シーケンスの全要素を二乗) • C++のラムダ式と比べてシンプルなのは • 型推論がやりやすい • const&とかがない • ガベージ コレクションがあるし、変数キャプチャ し放題
  113. 113. 匿名型 • 1か所でしか使わないような型は作らなくてい い source.GroupBy(p => new { p.X, p.Y }); (XとYでグループ化) • immutableなクラスを自動生成 • GetHashCode、等値比較、ToStringを完備
  114. 114. 拡張メソッド • 静的メソッドを後置き記法で書ける source.Select(x => x * x); 同じ意味 System.Linq.Enumerable.Select( source, x => x * x); C++でも関数オブジェクトに 対する演算子オーバーロード 似たことできなくはない • 単に語順を変えるだけ • 静的メソッドはpure functionである限り無害 • pure = 状態を持たない 同じ入力を与えたら常に同じ結果が帰ってくる
  115. 115. dynamic C# 4.0 動的コード生成 ダック タイピング
  116. 116. dynamic型 • C# 4.0のdynamic型 • 結構用途が限られてて • できること • 静的な型に対する動的コード生成 • DLR言語との連携 • できるけど過剰スペックなこと • JSONみたいなスキーマレスなデータ読み書き • できないこと • メタプログラミング • C#のスクリプト的実行
  117. 117. 主なターゲットは静的な型 • .NETの型情報に基づいた動的コード生成 • IDynamicMetaObjectProviderを実装すれば型情報な くてもコード生成できるけども、あまりやる人いな い • 利用例 • 多重ディスパッチとか • ダック タイピングとか
  118. 118. 多重ディスパッチ • 静的な型に対して class Base { } class A : Base { } class B : Base { }
  119. 119. 多重ディスパッチ • 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"; } }
  120. 120. 多重ディスパッチ • 呼び出し例 static void Main() { Dispatch(new A(), Dispatch(new A(), Dispatch(new B(), Dispatch(new B(), } new new new new A()); B()); B()); A()); // // // // A - A A - B others others static void Dispatch(Base x, Base y) { Console.WriteLine(x.Dispatch(y)); }
  121. 121. ダック タイピング • 静的な型に対して class Point { public int X { get; set; } public int Y { get; set; } public override string ToString() { return "Point(" + X + ", " + Y + ")"; } }
  122. 122. ダック タイピング • 同じ名前のメンバーを持つ別の型をまとめて処 理 static class Extensions { public static void CopyTo<T, U>(this T p, U q) { dynamic x = p; dynamic y = q; y.X = x.X; X, Yを持つ任意 y.Y = x.Y; の型に使える } }
  123. 123. ダック タイピング • 呼び出し例 var p = new Point(); new { X = 10, Y = 20 }.CopyTo(p); Console.WriteLine(p);
  124. 124. DLR連携 • 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]); • DLRは内部的に.NETの型を生成してるので内部実装 的にはこれも「静的な型に対する動的コード生成」
  125. 125. dynamicの内部実装 • コード生成結果 実際にはobject object X(object x) { if (_site1 == null) dynamic X(dynamic x) { { _site1 = CallSite<Func<CallSite, o return x.X; Binder.GetMember(CSharpBinderF } 動的コード生成 new CSharpArgumentInfo[] 用の情報 { CSharpArgumentInfo.Create( これが本体 })); } return _site1.Target(_site1, x); }
  126. 126. CallSite.Targetの中身 • メソッドを動的コード生成してる • 生成したメソッドはキャッシュして持っておく (inline method cache) _site1.Targetの初期状態 static object _anonymous(CallSite site, object x) { return site.Targetを更新する処理 }
  127. 127. CallSite.Targetの中身 • メソッドを動的コード生成してる • 生成したメソッドはキャッシュして持っておく (inline method cache) メソッドXにPoint型のインスタンスを渡した後 static object _anonymous(CallSite site, object x) { 1行追加 if (x is Point) return ((Point)x).X; 単なる型判定+キャスト else return site.Targetを更新する処理 } • 同じ型に対して何度も呼ぶ分には高性能
  128. 128. CallSite.Targetの中身 • メソッドを動的コード生成してる • 生成したメソッドはキャッシュして持っておく (inline method cache) さらに、メソッドXにVector3d型のインスタンスを渡した後 static object _anonymous(CallSite site, object x) { if (x is Point) return ((Point)x).X; if (x is Vector3D) return ((Vector3D)x).X; もう1行追加 else return site.Targetを更新する処理 } ちなみに、最近はスクリプト言語でも、 内部的に型を作って、inline method cache で高速化してる
  129. 129. “heavy” dynamic • 用途によっては高性能なものの… • 多くの場合、過剰スペック • できるけど過剰スペックな例 • JSONみたいなスキーマレスなデータ読み書き • もっと”light-weight”なdynamicが必要 (今のC#にはない。要望としてはある) 例: 規約ベースの置き換え x.X; x.Get("X"); x.M(); x.Invoke("M");
  130. 130. できないこと • メンバー名がコンパイル時に既知でないとダメ dynamic x = p; dynamic y = q; y.X = x.X; コンパイル時に既知 y.Y = x.Y; • なのでメタプログラミングには使えない • そのためにはもっと低レイヤーなAPI使う • IL Emit • Expression Tree • Roslyn
  131. 131. コード生成API 高級言語 (C#) Roslyn† parse 構文木 式ツリー (System.Linq.Expressions) emit ILGenerator IL JIT ネイティブ コード † 新しいC#コンパイラーのコードネーム 動的なコンパイルも可能
  132. 132. コード生成にかかる時間 • あんまり動的にやりたくない ある環境で、あるコードの生成結果の例 かかった時間[ミリ秒] ILGenerator (JIT) 39.89 Expressions (emit→JIT) 67.94 Roslyn (parse→emit→JIT) 4314 倍遅い 2桁遅い • 当たり前だけどparseは遅い • quoteほしい • できればコンパイル時生成でメタプログラミングしたい
  133. 133. async/await C# 5.0 非同期処理
  134. 134. 非同期処理しんどい • 普通に非同期処理やったらコールバック地獄 • begin/end地獄 • イベント地獄 • then地獄(ContinueWith地獄) x.BeginX(args, state, ar => x.XAsync() { .ContinueWith(t => var result = x.EndX(); { … var result = t.Result; x.XCompleted += (s, arg) => }); … { }; 非同期呼び出しが1回だから var result = arg.Result; この程度で済んでる … }); x.XAsync();
  135. 135. 面倒な非同期処理の例 • 複数の確認ダイアログ表示 確認フロー ゲームで アイテムを 合成します 確認 1 チェック レア アイテムですよ? Yes 合成強化済みですよ? Yes もう強化限界ですよ? No 確認 2 チェック ユーザーから の入力待ちも 非同期処理 Yes No 確認 3 チェック No 結果表示
  136. 136. 同期処理 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;
  137. 137. 非同期処理 (旧) 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); • 画面に収まるように フォント サイズ調整 • 4pt • ほんの84行ほど • ちなみに • 部分部分を関数化して多 少は整理できる • ダイアログ3つだからまだ この程度で済む
  138. 138. 非同期処理(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演算子が増えただけ • ダイアログの数が増えても平気
  139. 139. イテレーターと似た仕組み • イテレーター(ジェネレーター)があれば、割と 単純なラッパーで非同期処理も可能 • 要は、「中断と再開」 • 例えば、TypeScriptでは • 現状でもawaitのMS社内実装は持ってる • いまだと、生成されるJavaScriptが悲惨すぎて大変 • EcmaScriptにジェネレーターが実装されてから、 TypeScriptにawaitを追加する予定
  140. 140. 参考: C#のイテレーター(再) • 中断と再開 class MethodEnumerator : IEnumerator<int> { public int Current { get; private set; } private int _state = 0; public bool MoveNext() { switch (_state) { case 0: 状態の記録 Current = 1; Current = 1; _state = 1; _state = 1; return true; 中断 return 1: case true; case 1: = 2; Current _state = 2; return true; 再開用のラベル IEnumerable<int> Method() { yield return 1; yield return 2; case 2: } } } } default: return false;
  141. 141. 基本的な考え方 • 概念としては イテレーター+継続呼び出し 状態の記録 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; 結果の受け取り
  142. 142. 実際の展開結果 • 実際はもう少し複雑 • Awaiterというものを介する(Awaitableパターン) _state = 1; var awaiter1 = task1.GetAwaiter(); if (!awaiter1.IsCompleted) { awaiter1.OnCompleted(a); • Awaiterを自作することで、 awaitの挙動を変更可能 return; } • Task以外もawait可能 case 1: var x = awaiter1.GetResult();
  143. 143. 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; }
  144. 144. もう少し低レイヤーな話 • Taskクラス 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; • スレッド機能だけあればいいわけじゃない • Task Pool、同期コンテキストとか
  145. 145. スレッド • 非同期処理の最も低レイヤーな部分 • ただし、高負荷 for (int i = 0; i < 1000; i++) { var t = new Thread(Worker); t.Start(); } 1000個の処理を同時実行 • 細々と大量の処理(タスク)をこなすには向かない • 切り替え(コンテキスト スイッチ)のコストが高すぎる
  146. 146. 2種類のマルチタスク プリエンプティブ • • • • ハードウェア タイマーを使って強制割り込み OSが特権的にスレッド切り替えを行う 利点: 公平(どんなタスクも等しくOSに制御奪われる) 欠点: 高負荷(切り替えコストと使用リソース量) 協調的※ • • • • 各タスクが責任を持って終了する 1つのタスクが終わるまで次のタスクは始まらない 利点: 低負荷 欠点: 不公平(1タスクの裏切りが、全体をフリーズさせる) ※cooperative
  147. 147. スレッド プール • スレッドを可能な限り使いまわす仕組み • プリエンプティブなスレッド数本の上に • 協調的なタスク キューを用意 スレッド プール キュー 新規タスク タスク1 タスク2 … タスクは一度 キューに溜める 数本のスレッド だけ用意 空いているスレッドを探して実行 (長時間空かない時だけ新規スレッド作成) ちなみに、std::futureはThread Poolな実装になってるはず
  148. 148. スレッド プールの向上 • Work Stealing Queue • lock-free実装なローカル キュー • できる限りスレッド切り替えが起きないように グローバル キュー ① スレッドごとに キューを持つ ローカル キュー1 ローカル キュー2 スレッド1 スレッド2 ② ローカル キュー が空のとき、 他のスレッドから タスクを奪取
  149. 149. シングル スレッド必須なもの • スレッド安全なコードは高コスト • いっそ、単一スレッド動作を前提に • 典型例はGUI • C#/.NETに限らずたいていのGUIフレームワークはシング ル スレッド動作 • 低レイヤーAPI(DirectXとかOpenGLとか)も、1つのス レッドからしか扱えない
  150. 150. 典型例: UIスレッド • GUIは単一スレッド動作(UIスレッド) • ユーザーからの入力受け付け • 画面の更新 ユーザー からの入力 UIスレッド グラフィック 他のスレッド 更新 OK 処理中は 応答不可 他のスレッドから は更新不可
  151. 151. 矛盾 シングル スレッド推奨 単一スレッドからしか UI更新できない OK そのスレッドを止める とUIフリーズ マルチ スレッド推奨
  152. 152. 解決策 1. スレッド プールで重たい処理 2. UIスレッドに処理を戻してからUI更新 UIスレッド 他のスレッド Task.Run いったんUIスレッド にメッセージを渡す 重たい処理 Dispatcher.Invoke 更新 OK 渡す役割を担うのが ディスパッチャー※ ※ dispatcher: 配送者 他のGUIフレームワークだとevent queueとかhandlerとかいう 名前で提供されたりするものの、やってることは一緒
  153. 153. おまけ: immutable • 並列処理といえばimmutableだけども • 書き換えが起こらないなら複数のスレッドで共有し ても安全 • この用途だと、C++のconstは不十分 • あれは参照渡しを安全に行う用であって • 呼び出し元の側ではconstとは限らず、書き換わる 可能性あり • あと、mutable修飾子を付ければconstなオブジェク トすら書き換えれる
  154. 154. Compiler as a Service Future of C#, C# 6.0 コンパイラーの内部データを活用
  155. 155. Compiler as a Service • 今、C#はコンパイラーをフルスクラッチで作 り直してる • Code name “Roslyn” • C#実装のC#コンパイラー • コンパイラーの内部データを自由に使える
  156. 156. 用途 • C#スクリプティング • C#で設定ファイル書きたい(むしろXMLがいや) • アプリの再起動なしでロジック更新 • ウェブ サービスで使う • (GitHubとか想像してもらって)コード リポジトリの インデックス化 • オンラインIDE、ビルド、テスト • IDE連携
  157. 157. 構文ハイライト • 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(); }
  158. 158. リアルタイム エラー検出 • エラー検出タイミングがビルド時とか遅い • Visual Studioは常時やってる
  159. 159. コード補完 • タイピングめんどくさい • 文法やライブラリ、いちいち覚えたくない
  160. 160. リファクタリング • 最初からきれいなコード書くのめんどくさい • でもほっときたくない
  161. 161. ということで • 今、コンパイラーに求められる要件 • ソースコードのどこからどこまで(何行何列目)が 何かという情報がとれる • 文脈に応じてソースコードを生成したり、書き替え たりできる • リアルタイム処理を必要とするので、パフォーマン スも求められる
  162. 162. 実はこれまで • コンパイラー(パーサー)を2重開発してた • コンパイル用 • IDE用 • まして、サード パーティ製コード解析プラグイン も含めると、3重開発 • Java (EclipseとかIntelliJとか)でも • 「Eclipseが対応するまでJava 8プレビュー試せない や」
  163. 163. Clang • Clangはその辺りをゴールの1つに掲げてる • IDEで使う前提 • リファクタリングに使いやすいデータ構造の構文木 • インクリメンタル コンパイルとかもしやすい
  164. 164. IDE連携まで考えると • 簡単そうに見える文法ですら、実装コストかな り高い • 2重開発はそれだけ大変 • だからIDEを信用しない/できない人も多い • IDE対応待ってられない/待ちたくない • “Roslyn”はそれを解消 • C#に新機能を足しやすくなる • C# 6.0
  165. 165. C# 6.0 (予定)の例 Primary Constructor / Property Expressions public class Point(int x, int y) { public int X => x; immutableな型を作りやすく public int Y => y; } Declaration Expressions 「式」で書けることの幅が広がる while ((var line = stream.ReadLine()) != null) line ... if ((var x = obj as Point) != null) x ...
  166. 166. 以上。
  167. 167. まとめ • .NET Frameworkの基礎機能 • • • • Garbage Collection AppDomain メタデータ、JIT PCL • C# 2.0~5.0… 6.0! • ジェネリック、イテレーター • LINQ、dynamic、async/await • Compiler as a Service

×