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

  • 42,177 views
Uploaded on

2014/3/1 Boost勉強会 #14 東京 にて …

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

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

More in: Technology
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
  • Can you make this slides in english for reach more people

    http://www.microsoft-csharp-net.blogspot.in
    Are you sure you want to
    Your message goes here
  • 補足しておくと、Work Stealing Queue は lock-free である必要はなく (.NET だと実際問題 lock-free ではない)、Global queue への lock contention rate を下げるのが目的。Default の実装における Work Stealing Queue においてスレッド切り替えが減少する理由は lock-free が原因ではなく、locality を持つケースが多いため、関連するタスクが同じ local queue に詰まれたほうが効率がよいという理由からです。(ContinueWith() のような連結が多いことに起因している)
    Are you sure you want to
    Your message goes here
No Downloads

Views

Total Views
42,177
On Slideshare
0
From Embeds
0
Number of Embeds
35

Actions

Shares
Downloads
235
Comments
2
Likes
122

Embeds 0

No embeds

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
    No notes for slide

Transcript

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