C#とILとネイティブと
Upcoming SlideShare
Loading in...5
×

Like this? Share it with your network

Share

C#とILとネイティブと

  • 15,178 views
Uploaded on

2013/12/21 ...

2013/12/21
プログラミング生放送勉強会 第27回@品川
にて発表。

More in: Technology
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Be the first to comment
No Downloads

Views

Total Views
15,178
On Slideshare
6,118
From Embeds
9,060
Number of Embeds
18

Actions

Shares
Downloads
35
Comments
0
Likes
21

Embeds 9,060

http://ufcpp.wordpress.com 8,348
https://ufcpp.wordpress.com 368
https://twitter.com 157
http://feedly.com 115
http://www.feedspot.com 25
http://newsblur.com 17
https://www.facebook.com 8
http://s.deeeki.com 5
http://webcache.googleusercontent.com 4
http://cache.yahoofs.jp 3
http://www.newsblur.com 3
https://m.facebook.com&_=1387694554645 HTTP 1
https://www.google.co.jp 1
http://news.google.com 1
http://127.0.0.1 1
http://reader.aol.com 1
http://digg.com 1
https://www.chatwork.com 1

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#とILとネイティブと 岩永 信之
  • 2. 本日の内容 • C#コードのコンパイル過程 • どうしてIL (.NETの中間言語)なのか • ライブラリのコード変更の影響 • ソースコード配付でない理由 • ネイティブ コード配付でない理由 • ネイティブ コード化 • Ngen、MDIL、Project “N”
  • 3. C#、ILの コンパイルの過程 C# → IL → ネイティブ
  • 4. コンパイル C# コード C#コンパイラー IL JITコンパイラー ネイティブ コード
  • 5. コンパイル .NET言語 C#の他にも • Visual Basic C#コンパイラー • F# • … C# コード IL static int GetVolume(Point p) JITコンパイラー { return p.X * p.Y * p.Z; } ネイティブ コード
  • 6. コンパイル C# コード C#コンパイラー IL JITコンパイラー ネイティブ コード Intermediate Language .NET共通の中間言語 .maxstack IL_0000: IL_0001: IL_0006: IL_0007: IL_000c: IL_000d: IL_000e: IL_0013: IL_0014: 8 ldarg.0 ldfld int32 Point::X ldarg.0 ldfld int32 Point::Y mul ldarg.0 ldfld int32 Point::Z mul ret
  • 7. コンパイル C# コード C#コンパイラー IL JITコンパイラー ネイティブ コード ネイティブ コード CPUごとの命令列 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
  • 8. 例 • (C#で)こんな型があったとして public struct Point { public int X; public int Y; public int Z; } • 整数のフィールドを3つ持つ
  • 9. 例 • こんなメソッドを書いたとする static int GetVolume(Point p) { return p.X * p.Y * p.Z; } • フィールドの掛け算
  • 10. 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 }
  • 11. ネイティブ コード • 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とかの数値に 型情報は残らない
  • 12. メモリ レイアウト • この4とか8の意味 Point X public struct Point { public int X; public int Y; public int Z; } Y 4バイト 8バイト Z ※レイアウトがどうなるかは環境依存
  • 13. メモリ レイアウト • この4とか8の意味 ILの時点までは名前 で参照してる public struct Point Point X { public int X; public int Y; public int Z; } Y 4バイト 8バイト ネイティブ コードは レイアウトを見て Z 数値で参照してる ※レイアウトがどうなるかは環境依存
  • 14. 数値でのフィールド参照 • 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)
  • 15. ここまでまとめ • メモリ レイアウト C# コード IL ネイティブ コード ILまでは型情報を残している フィールド名で値を参照 ネイティブ コードになると型情報が残らない レイアウト上のオフセット数値で値を参照
  • 16. おまけ: ILの見方 • ildasm.exe • Program Files以下 • Microsoft SDKsWindows[Windowsのバージョ ン]binNETFX[.NETのバージョン]
  • 17. おまけ: Nativeの見方 • Visual Studioのメニューから • デバッグ実行時に • [デバッグ] → [ウィンドウ] → [逆アセンブル] • Ctrl+Alt+D
  • 18. コード変更の影響
  • 19. 依存関係 • 開発体制としてありがちな状況 他社製 ライブラリ 自社製 ライブラリ アプリ
  • 20. 依存関係 • もちろん、実際はもっと複雑な依存関係が • 多対多 • 多段 • 開発者と利用者は別 • • • • 別人 別チーム 別会社 他国
  • 21. 今回の例でいうと • Point型の開発者と利用者がわかれてると仮定 他社製 ライブラリ public struct Point { public int X; public int Y; public int Z; } 自社製 ライブラリ int GetVolume(Point p) { return p.X * p.Y * p.Z; } • この状況で、Point型への変更が GetVolumeメソッド側に及ぼす影響を考える
  • 22. 変更してみる • 大して影響しなさそうな ほんの些細な変更をしてみる public struct Point { public int X; public int Y; public int Z; } public struct Point { public int X; public int Z; public int Y; } フィールドの順序変更
  • 23. その結果起きること • メモリ レイアウトが変わる※ Point Point X Y Z Z ※ X Y この例(フィールド)以外にも、仮想メソッド テーブルとかいろいろ変わる
  • 24. C#レベルでの影響 • 影響なし • Point構造体がX, Y, Zという名前の3つのintを持って ることには変わりない 利用側(GetVolumeメソッド側)は 変更があったことを知る必要すらない 再コンパイルの必要なし
  • 25. 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が吸収してくれる
  • 26. ネイティブ レベルでの影響 • ここで影響が出る 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 利用側の再コンパイルが必要 ライブラリ側だけの差し替えじゃダメ
  • 27. ここまでまとめ • プログラムのほんの些細な変更によって • メモリ レイアウトが変化する • 利用側(ライブラリ側とは別人が保守)に影響 C#、JITレベルだと影響なし ネイティブ レベルだと影響が出る • .NET製プログラム/ライブラリ をILの状態で配布する 最大の理由 • 事前のネイティブ化が難しい
  • 28. ソースコード配布 スクリプト言語ならそんな問題こと考えなくていいよ? ただし… 遅い
  • 29. ネイティブ配布しないとして • ILなんていう中途半端な状態にする必要ある の? • スクリプト言語みたいにソースコード配付すれば? • 理由はパフォーマンス • 測ってみよう
  • 30. 方法 • C#には動的に(C#プログラム中で)コンパイ ルする仕組みがいくつかあるので • C#コードをコンパイル • 構文木からコード生成 • ILコンパイル この辺りを使って比較
  • 31. コンパイルの過程 高級言語 (C#) parse 構文木 emit IL JIT ネイティブ コード
  • 32. コンパイルの過程 高級言語 (C#) parse 構文木 emit IL JIT ネイティブ コード • parse (構文解析、文法説明) • (C#みたいな)自然言語に近い 文法を解析 • コンパイラーが扱いやすい データ形式に変換
  • 33. コンパイルの過程 高級言語 (C#) parse 構文木 emit IL JIT ネイティブ コード • emit (放射、発行) • コンピューターが解釈できる 命令を出力する • (.NETの場合、CPU命令じゃな くて、ILの仮想マシン命令) • (もちろん言語によっては直接 CPUネイティブ命令を出力)
  • 34. コンパイルの過程 高級言語 (C#) parse 構文木 emit IL JIT ネイティブ コード • JIT (Just In Time) • プログラムを初めて実行する その瞬間に • IL仮想マシン命令からCPUネ イティブ命令に変換
  • 35. 動的コンパイル用ライブラリ 高級言語 (C#) Roslyn parse 構文木 式ツリー (System.Linq.Expressions) emit ILGenerator IL JIT ネイティブ コード
  • 36. 例 • さっきの↓これを生成してみる static int GetVolume(Point p) { return p.X * p.Y * p.Z; }
  • 37. C#コードから • Roslyn※ var engine = new ScriptEngine(); var session = engine.CreateSession(); session.AddReference(typeof(Point).Assembly); session.ImportNamespace("System"); session.ImportNamespace("MyNamespace"); return session.Execute<Func<Point, int>>( "p => p.X * p.Y * p.Z"); ※ コードネーム(まだ正式名称じゃない) 今開発中の新しいC#コンパイラー C#をスクリプト言語的に実行する機能も持つ
  • 38. C#コードから • Roslyn ポイントの行: session.Execute<Func<Point, int>>("p => p.X * p.Y * p.Z") C#ソースコードをコンパイル parse→emit→JIT C#→[parse]→構文木→[emit]→IL→[JIT]→Native
  • 39. 構文木から • System.Linq.Expressions var var var var var t x y z p = = = = = typeof(Point); t.GetField("X"); t.GetField("Y"); t.GetField("Z"); Expression.Parameter(typeof(Point)); var ex = Expression.Lambda<Func<Point, int>>( Expression.Multiply( Expression.Multiply( Expression.Field(p, x), Expression.Field(p, y)), Expression.Field(p, z)), p); return ex.Compile();
  • 40. 構文木から • System.Linq.Expressions ポイントの行: Expression.Lambda<Func<Point, int>>( Expression.Multiply( Expression.Multiply( Expression.Field(p, x), Expression.Field(p, y)), Expression.Field(p, z)),
  • 41. 構文木から • 図で表すと 匿名関数を作る 本体 パラメーター Multiply pから Zを読む p Multiply pから Yを読む pから Xを読む C#→[parse]→構文木→[emit]→IL→[JIT]→Native
  • 42. ILを直接生成 • ILGenerator var t = typeof(Point); var x = t.GetField("X"); var y = t.GetField("Y"); var z = t.GetField("Z"); var m = new DynamicMethod("GetVolume", typeof(int), new[] { t }); m.DefineParameter(1, ParameterAttributes.In, "p"); var gen = m.GetILGenerator(); gen.Emit(OpCodes.Ldarg_0); gen.Emit(OpCodes.Ldfld, x); gen.Emit(OpCodes.Ldarg_0); gen.Emit(OpCodes.Ldfld, y); gen.Emit(OpCodes.Mul); gen.Emit(OpCodes.Ldarg_0); gen.Emit(OpCodes.Ldfld, z); gen.Emit(OpCodes.Mul); gen.Emit(OpCodes.Ret);
  • 43. ILを直接生成 • ILGenerator ポイントの行: var gen = m.GetILGenerator(); gen.Emit(OpCodes.Ldarg_0); gen.Emit(OpCodes.Ldfld, x); gen.Emit(OpCodes.Ldarg_0); gen.Emit(OpCodes.Ldfld, y); gen.Emit(OpCodes.Mul); gen.Emit(OpCodes.Ldarg_0); gen.Emit(OpCodes.Ldfld, z); gen.Emit(OpCodes.Mul); gen.Emit(OpCodes.Ret);
  • 44. ILを直接生成 • さっき見せたILコードと一緒 IL_0000: IL_0001: IL_0006: IL_0007: IL_000c: IL_000d: IL_000e: IL_0013: IL_0014: ldarg.0 ldfld int32 Point::X ldarg.0 ldfld int32 Point::Y mul ldarg.0 ldfld int32 Point::Z mul ret gen.Emit(OpCodes.Ldarg_0); gen.Emit(OpCodes.Ldfld, x); gen.Emit(OpCodes.Ldarg_0); gen.Emit(OpCodes.Ldfld, y); gen.Emit(OpCodes.Mul); gen.Emit(OpCodes.Ldarg_0); gen.Emit(OpCodes.Ldfld, z); gen.Emit(OpCodes.Mul); gen.Emit(OpCodes.Ret); C#→[parse]→構文木→[emit]→IL→[JIT]→Native
  • 45. 実行 • コード生成+呼び出しを1000回ループ かかった時間[ミリ秒] ILGenerator (JIT) 39.89 Expressions (emit→JIT) 67.94 倍遅い 2桁遅い Roslyn 4314 (parse→emit→JIT) ※うちのPCでの計測の場合 ※ 測るたびに1割程度はぶれる
  • 46. 要するに 高級言語 (C#) parse 構文木 emit IL JIT ネイティブ コード ここがダントツで重い 残りの部分より2桁くらい遅い スクリプト言語だと このコストが丸ごとかかる
  • 47. 補足: このコストは初回のみ • 何度も同じメソッドを呼ぶ場合 負担はだいぶ小さい GetVolume(p1); GetVolume(p2); GetVolume(p3); GetVolume(p4); ※ JITとか※の処理はここでだけ発生 ネイティブ化済みの コードを実行 C#みたいにIL配布する場合はJITのみ スクリプト言語みたいにソースコード配付する場合はparse→emit→JIT
  • 48. 補足: このコストは初回のみ とはいえ • JITですら遅いって言われる • どうしてもネイティブと比べられる (比較対象があると結構気になる) • 特に、起動直後に遅くなる (最初が肝心なのに) • これでも、C#は構文的にparseが早い部類 (構文によってはもっと遅い)
  • 49. ここまでまとめ • やっぱ事前ネイティブ化の要求はある • JITのコストですら嫌われる • 特に、起動直後の遅さ • ましてparseからやるとさらに2桁遅い
  • 50. 事前ネイティブ化 「やっぱり最初からネイティブにしたい」となったとき に
  • 51. 選択肢 • 配付形式の選択肢 • ネイティブか • 中間形式(IL)か • ソースコード(スクリプト)か どれがいい? • 状況によるので選びたい • さらに別の選択肢: 部分的にネイティブ化
  • 52. おまけ: 連携も1つの選択肢 • スクリプト言語との連携 • DLR (Dynamic Language Runtime) • スクリプト言語実装基板 • Iron Pythonなど • スクリプト言語の.NET実装 • ネイティブとの連携 • WinMD (Windows Metadata) • .NETの型情報を.NET以外でも使う • WinRT (Windows Runtime) • .NETからも参照しやすいネイティブ ライブラリ 連携もいいんだけど、C#使いたい…
  • 53. C#でスクリプティング • ASP.NET • C#ソースコードのままサイトに配置可能 • 初回アクセス時が異様に遅かった※ものの… ASP.NET 4/IIS 7.5からは自動実行に • Azure Websites上のコードならオンライン編集可能 • Visual Studio Online “Monaco”† • Roslyn (新しいC#コンパイラー) • C#をスクリプト的に実行する機能も持つ ※ 初回アクセス時にコンパイルしてた コンパイル(parseから)の遅さは先ほどの例の通り † コードネーム(まだ正式名称じゃない) ブラウザ上で編集できるエディター
  • 54. C#でネイティブ • どの道、最終的にはネイティブ化(JIT)してるん だから • ネイティブ化自体はそんなに難しくない • Mono ※はAOT†コンパイル機能を持ってる • 問題は「コード変更の影響」で説明した通り • ライブラリ側のレイアウトとかが変わった時の再コ ンパイル Ngen → Auto-Ngen → MDIL → Project “N ” ※ .NET Framework互換のオープンソース実装 † Ahead of Time: ビルドの時点でネイティブ コードにコンパイル
  • 55. 補足: ネイティブだけど • ここでいう「C#でネイティブ」「.NETアプリ のネイティブ化」とは • IL命令をCPUネイティブ命令に置き替えること • CPUネイティブ命令であってもmanaged • .NETランタイムを参照していて、メモリ操作などは .NET Frameworkの管理下
  • 56. Ngen • Ngen.exe • Native Image Generator • ILを事前にネイティブ化するためのツール • 自前管理が必要 • アプリのインストーラー※とかを作って明示的に呼び出し • 参照しているライブラリが更新された時には呼びなおす 必要あり • かなり面倒なのでアプリを Ngenすることはめったにない • .NET自体が標準ライブラリの 高速化のために使ってる ※ 要するに、JITの負担を起動時じゃなくてインストール時に前倒しする
  • 57. Auto-Ngen • .NET Framework 4.5以降なら • NgenがWindowsサービスとして常に動いてる • アイドル時に動作 • 利用頻度の高いものを自動的にNgen • デスクトップ アプリの場合はGACアセンブリのみ • Windowsストア アプリの場合はすべてのアセンブリ • よく使うアプリの起動はだいぶ早くなる • インストール直後の起動は相変わらず遅い
  • 58. 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
  • 59. 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
  • 60. 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実機上では レイアウトの解決(リンク)だけ行う • インストール直後の起動も安心
  • 61. Project N • コードネーム“Project N”※ • C#/.NETコードを直接(ビルド時に)ネイティブ化す る話も出てるみたい • 詳細はまだ何も語られてない • Auto-NgenとかMDILの流れをくむものっぽい ※ http://www.zdnet.com/microsoft-shows-off-its-next-generation-project-n-compiler-technology-7000023156/
  • 62. ここまでまとめ • 事前ネイティブ化はそんなに楽じゃない • ライブラリのコード変更の影響を吸収しないといけ ない • かといってアプリ起動のたびにJITしたくない • Auto-Ngen → MDIL → Project N? • アイドル時間を使ってネイティブ化したり • 部分的にネイティブ化したり
  • 63. まとめ • C# → IL(中間言語) → ネイティブ • ILの意義 • ライブラリのコード変更の影響を吸収 • ソースコード配付よりはだいぶ高速 • ネイティブ コード化 • Auto-Ngen → MDIL → Project N • アイドル時間を使ってネイティブ化したり • 部分的にネイティブ化したり