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