C#とILとネイティブと
岩永 信之
本日の内容
• C#コードのコンパイル過程
• どうしてIL (.NETの中間言語)なのか
• ライブラリのコード変更の影響
• ソースコード配付でない理由
• ネイティブ コード配付でない理由

• ネイティブ コード化
• Ngen、MDI...
C#、ILの
コンパイルの過程
C# → IL → ネイティブ
コンパイル
C#
コード
C#コンパイラー

IL
JITコンパイラー

ネイティブ
コード
コンパイル
.NET言語
C#の他にも
• Visual Basic
C#コンパイラー • F#
• …

C#
コード

IL

static int GetVolume(Point p)
JITコンパイラー {
return p.X * p...
コンパイル
C#
コード
C#コンパイラー

IL
JITコンパイラー

ネイティブ
コード

Intermediate Language
.NET共通の中間言語
.maxstack
IL_0000:
IL_0001:
IL_0006:
IL_...
コンパイル
C#
コード
C#コンパイラー

IL
JITコンパイラー

ネイティブ
コード

ネイティブ コード
CPUごとの命令列
push
mov
cmp
je
call
mov
lea
imul
lea
imul
pop
ret

eb...
例
• (C#で)こんな型があったとして
public struct Point
{
public int X;
public int Y;
public int Z;
}

• 整数のフィールドを3つ持つ
例
• こんなメソッドを書いたとする
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...
ネイティブ コード
• JIT結果 (x64の場合)
push
mov
cmp
je
call
mov
lea
imul
lea
imul
pop
ret

ebp
ebp,esp
dword ptr ds:[5011058h],0
00FE2...
メモリ レイアウト
• この4とか8の意味

Point
X

public struct Point
{
public int X;
public int Y;
public int Z;
}

Y

4バイト

8バイト

Z

※レイアウ...
メモリ レイアウト
• この4とか8の意味
ILの時点までは名前
で参照してる
public struct Point

Point
X

{
public int X;
public int Y;
public int Z;
}

Y

4バ...
数値でのフィールド参照
• C#で擬似的に書くと
static int GetVolume(Point p)
{
return p.X * p.Y * p.Z;
}
var pp = (byte*)&p;
var x = *((int*)pp)...
ここまでまとめ
• メモリ レイアウト
C#
コード

IL

ネイティブ
コード

ILまでは型情報を残している
フィールド名で値を参照

ネイティブ コードになると型情報が残らない
レイアウト上のオフセット数値で値を参照
おまけ: ILの見方
• ildasm.exe
• Program Files以下
• Microsoft SDKsWindows[Windowsのバージョ
ン]binNETFX[.NETのバージョン]
おまけ: Nativeの見方
• Visual Studioのメニューから
• デバッグ実行時に
• [デバッグ] → [ウィンドウ] → [逆アセンブル]
• Ctrl+Alt+D
コード変更の影響
依存関係
• 開発体制としてありがちな状況
他社製
ライブラリ

自社製
ライブラリ

アプリ
依存関係
• もちろん、実際はもっと複雑な依存関係が
• 多対多
• 多段

• 開発者と利用者は別
•
•
•
•

別人
別チーム
別会社
他国
今回の例でいうと
• Point型の開発者と利用者がわかれてると仮定
他社製
ライブラリ
public struct Point
{
public int X;
public int Y;
public int Z;
}

自社製
ライブラリ
...
変更してみる
• 大して影響しなさそうな
ほんの些細な変更をしてみる
public struct Point
{
public int X;
public int Y;
public int Z;
}

public struct Point
...
その結果起きること
• メモリ レイアウトが変わる※
Point

Point

X

Y

Z

Z

※

X

Y

この例(フィールド)以外にも、仮想メソッド テーブルとかいろいろ変わる
C#レベルでの影響
• 影響なし

• Point構造体がX, Y, Zという名前の3つのintを持って
ることには変わりない
利用側(GetVolumeメソッド側)は
変更があったことを知る必要すらない
再コンパイルの必要なし
ILレベルでの影響
• 影響なし
IL_0000:
IL_0001:
IL_0006:
IL_0007:
IL_000c:
IL_000d:
IL_000e:
IL_0013:
IL_0014:

ldarg.0
ldfld
ldarg.0
l...
ネイティブ レベルでの影響
• ここで影響が出る
push
mov
cmp
je
call
mov
lea
imul
lea
imul
pop
ret

ebp
ebp,esp
dword ptr ds:[5011058h],0
00FE2A0...
ここまでまとめ
• プログラムのほんの些細な変更によって
• メモリ レイアウトが変化する
• 利用側(ライブラリ側とは別人が保守)に影響
C#、JITレベルだと影響なし
ネイティブ レベルだと影響が出る
• .NET製プログラム/ライブラリ
...
ソースコード配布
スクリプト言語ならそんな問題こと考えなくていいよ?
ただし… 遅い
ネイティブ配布しないとして
• ILなんていう中途半端な状態にする必要ある
の?
• スクリプト言語みたいにソースコード配付すれば?

• 理由はパフォーマンス
• 測ってみよう
方法
• C#には動的に(C#プログラム中で)コンパイ
ルする仕組みがいくつかあるので
• C#コードをコンパイル
• 構文木からコード生成
• ILコンパイル
この辺りを使って比較
コンパイルの過程
高級言語
(C#)

parse
構文木

emit
IL

JIT
ネイティブ
コード
コンパイルの過程
高級言語
(C#)

parse
構文木

emit
IL

JIT
ネイティブ
コード

• parse (構文解析、文法説明)
• (C#みたいな)自然言語に近い
文法を解析
• コンパイラーが扱いやすい
データ形式に変換
コンパイルの過程
高級言語
(C#)

parse
構文木

emit
IL

JIT
ネイティブ
コード

• emit (放射、発行)
• コンピューターが解釈できる
命令を出力する
• (.NETの場合、CPU命令じゃな
くて、ILの仮想...
コンパイルの過程
高級言語
(C#)

parse
構文木

emit
IL

JIT
ネイティブ
コード

• JIT (Just In Time)
• プログラムを初めて実行する
その瞬間に
• IL仮想マシン命令からCPUネ
イティブ命令...
動的コンパイル用ライブラリ
高級言語
(C#)

Roslyn

parse
構文木

式ツリー
(System.Linq.Expressions)

emit
ILGenerator

IL

JIT
ネイティブ
コード
例
• さっきの↓これを生成してみる
static int GetVolume(Point p)
{
return p.X * p.Y * p.Z;
}
C#コードから
• Roslyn※
var engine = new ScriptEngine();
var session = engine.CreateSession();
session.AddReference(typeof(Point...
C#コードから
• Roslyn
ポイントの行:
session.Execute<Func<Point, int>>("p => p.X * p.Y * p.Z")

C#ソースコードをコンパイル
parse→emit→JIT
C#→[pars...
構文木から
• System.Linq.Expressions
var
var
var
var
var

t
x
y
z
p

=
=
=
=
=

typeof(Point);
t.GetField("X");
t.GetField("Y")...
構文木から
• System.Linq.Expressions
ポイントの行:
Expression.Lambda<Func<Point, int>>(
Expression.Multiply(
Expression.Multiply(
Exp...
構文木から
• 図で表すと

匿名関数を作る
本体

パラメーター

Multiply
pから
Zを読む

p

Multiply

pから
Yを読む

pから
Xを読む

C#→[parse]→構文木→[emit]→IL→[JIT]→Nati...
ILを直接生成
• ILGenerator
var t = typeof(Point);
var x = t.GetField("X");
var y = t.GetField("Y");
var z = t.GetField("Z");
va...
ILを直接生成
• ILGenerator
ポイントの行:
var gen = m.GetILGenerator();
gen.Emit(OpCodes.Ldarg_0);
gen.Emit(OpCodes.Ldfld, x);
gen.Emi...
ILを直接生成
• さっき見せたILコードと一緒
IL_0000:
IL_0001:
IL_0006:
IL_0007:
IL_000c:
IL_000d:
IL_000e:
IL_0013:
IL_0014:

ldarg.0
ldfld i...
実行
• コード生成+呼び出しを1000回ループ
かかった時間[ミリ秒]
ILGenerator
(JIT)

39.89

Expressions
(emit→JIT)

67.94

倍遅い

2桁遅い

Roslyn
4314
(pars...
要するに
高級言語
(C#)

parse
構文木

emit
IL

JIT
ネイティブ
コード

ここがダントツで重い
残りの部分より2桁くらい遅い

スクリプト言語だと
このコストが丸ごとかかる
補足: このコストは初回のみ
• 何度も同じメソッドを呼ぶ場合
負担はだいぶ小さい
GetVolume(p1);
GetVolume(p2);
GetVolume(p3);
GetVolume(p4);

※

JITとか※の処理はここでだけ発...
補足: このコストは初回のみ
とはいえ
• JITですら遅いって言われる
• どうしてもネイティブと比べられる
(比較対象があると結構気になる)
• 特に、起動直後に遅くなる
(最初が肝心なのに)

• これでも、C#は構文的にparseが早い...
ここまでまとめ
• やっぱ事前ネイティブ化の要求はある
• JITのコストですら嫌われる
• 特に、起動直後の遅さ

• ましてparseからやるとさらに2桁遅い
事前ネイティブ化
「やっぱり最初からネイティブにしたい」となったとき
に
選択肢
• 配付形式の選択肢
• ネイティブか
• 中間形式(IL)か
• ソースコード(スクリプト)か

どれがいい?
• 状況によるので選びたい
• さらに別の選択肢: 部分的にネイティブ化
おまけ: 連携も1つの選択肢
• スクリプト言語との連携

• DLR (Dynamic Language Runtime)
• スクリプト言語実装基板

• Iron Pythonなど
• スクリプト言語の.NET実装

• ネイティブとの連...
C#でスクリプティング
• ASP.NET
• C#ソースコードのままサイトに配置可能
• 初回アクセス時が異様に遅かった※ものの…
ASP.NET 4/IIS 7.5からは自動実行に

• Azure Websites上のコードならオンライン...
C#でネイティブ
• どの道、最終的にはネイティブ化(JIT)してるん
だから
• ネイティブ化自体はそんなに難しくない
• Mono ※はAOT†コンパイル機能を持ってる

• 問題は「コード変更の影響」で説明した通り
• ライブラリ側のレイ...
補足: ネイティブだけど
• ここでいう「C#でネイティブ」「.NETアプリ
のネイティブ化」とは
• IL命令をCPUネイティブ命令に置き替えること
• CPUネイティブ命令であってもmanaged
• .NETランタイムを参照していて、メモ...
Ngen
• Ngen.exe
• Native Image Generator
• ILを事前にネイティブ化するためのツール
• 自前管理が必要
• アプリのインストーラー※とかを作って明示的に呼び出し
• 参照しているライブラリが更新された...
Auto-Ngen
• .NET Framework 4.5以降なら

• NgenがWindowsサービスとして常に動いてる
• アイドル時に動作

• 利用頻度の高いものを自動的にNgen
• デスクトップ アプリの場合はGACアセンブリの...
MDIL (ネイティブのおさらい)
• おさらい: ネイティブ コードだと
push
mov
cmp
je
call
mov
lea
imul
lea
imul
pop
ret

ebp
ebp,esp
dword ptr ds:[501105...
MDIL (部分的にネイティブ化)
• じゃあ、こんな形式があればいいんじゃ?
push
mov
cmp
je
call
mov
lea
imul
lea
imul
pop
ret

ほぼネイティブ

ebp
レイアウトのところ
ebp,esp...
MDIL (Compile in the Cloud)
• 実際、Windows Phoneのストアでは
そういう形式が使われている: MDIL
(Machine Dependent Intermediate Language)
C#コード
C...
Project N
• コードネーム“Project N”※
• C#/.NETコードを直接(ビルド時に)ネイティブ化す
る話も出てるみたい
• 詳細はまだ何も語られてない
• Auto-NgenとかMDILの流れをくむものっぽい

※ htt...
ここまでまとめ
• 事前ネイティブ化はそんなに楽じゃない
• ライブラリのコード変更の影響を吸収しないといけ
ない

• かといってアプリ起動のたびにJITしたくない
• Auto-Ngen → MDIL → Project N?

• アイド...
まとめ
• C# → IL(中間言語) → ネイティブ
• ILの意義
• ライブラリのコード変更の影響を吸収
• ソースコード配付よりはだいぶ高速

• ネイティブ コード化

• Auto-Ngen → MDIL → Project N
•...
Upcoming SlideShare
Loading in...5
×

C#とILとネイティブと

15,843

Published on

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

Published in: Technology
0 Comments
24 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total Views
15,843
On Slideshare
0
From Embeds
0
Number of Embeds
20
Actions
Shares
0
Downloads
36
Comments
0
Likes
24
Embeds 0
No embeds

No notes for slide

Transcript of "C#とILとネイティブと"

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

    Clipping is a handy way to collect important slides you want to go back to later.

×