[解決済み] コンパイルされたC#ラムダ式のパフォーマンス
質問
コレクションに対する次のような簡単な操作を考えてみましょう。
static List<int> x = new List<int>() { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
var result = x.Where(i => i % 2 == 0).Where(i => i > 5);
では、Expressionsを使ってみましょう。次のようなコードがおおよそ相当します。
static void UsingLambda() {
Func<IEnumerable<int>, IEnumerable<int>> lambda = l => l.Where(i => i % 2 == 0).Where(i => i > 5);
var t0 = DateTime.Now.Ticks;
for (int j = 1; j < MAX; j++)
var sss = lambda(x).ToList();
var tn = DateTime.Now.Ticks;
Console.WriteLine("Using lambda: {0}", tn - t0);
}
しかし、オンザフライで式を構築したいので、新しいテストを紹介します。
static void UsingCompiledExpression() {
var f1 = (Expression<Func<IEnumerable<int>, IEnumerable<int>>>)(l => l.Where(i => i % 2 == 0));
var f2 = (Expression<Func<IEnumerable<int>, IEnumerable<int>>>)(l => l.Where(i => i > 5));
var argX = Expression.Parameter(typeof(IEnumerable<int>), "x");
var f3 = Expression.Invoke(f2, Expression.Invoke(f1, argX));
var f = Expression.Lambda<Func<IEnumerable<int>, IEnumerable<int>>>(f3, argX);
var c3 = f.Compile();
var t0 = DateTime.Now.Ticks;
for (int j = 1; j < MAX; j++)
var sss = c3(x).ToList();
var tn = DateTime.Now.Ticks;
Console.WriteLine("Using lambda compiled: {0}", tn - t0);
}
もちろん上記と全く同じというわけではないので、公平を期すため、最初のものを少し修正します。
static void UsingLambdaCombined() {
Func<IEnumerable<int>, IEnumerable<int>> f1 = l => l.Where(i => i % 2 == 0);
Func<IEnumerable<int>, IEnumerable<int>> f2 = l => l.Where(i => i > 5);
Func<IEnumerable<int>, IEnumerable<int>> lambdaCombined = l => f2(f1(l));
var t0 = DateTime.Now.Ticks;
for (int j = 1; j < MAX; j++)
var sss = lambdaCombined(x).ToList();
var tn = DateTime.Now.Ticks;
Console.WriteLine("Using lambda combined: {0}", tn - t0);
}
MAX = 100000、VS2008、デバッグONの場合の結果です。
Using lambda compiled: 23437500
Using lambda: 1250000
Using lambda combined: 1406250
そして、デバッグOFFの状態で
Using lambda compiled: 21718750
Using lambda: 937500
Using lambda combined: 1093750
サプライズ . コンパイルされた式は、他の選択肢に比べておよそ17倍も遅いのです。さて、ここからが問題です。
- 等価でない式を比較しているのか。
- コンパイルした式を .NET に "最適化" させる機構はありますか?
-
同じチェーンコールをどのように表現すればよいですか。
l.Where(i => i % 2 == 0).Where(i => i > 5);
をプログラム的に表現するには?
さらにいくつかの統計。Visual Studio 2010 で、デバッグをオン、最適化をオフにしました。
Using lambda: 1093974
Using lambda compiled: 15315636
Using lambda combined: 781410
デバッギングON、最適化ON。
Using lambda: 781305
Using lambda compiled: 15469839
Using lambda combined: 468783
デバッグはOFF、最適化はON。
Using lambda: 625020
Using lambda compiled: 14687970
Using lambda combined: 468765
新しい驚き。
VS2008(C#3)からVS2010(C#4)に切り替えたことによって
UsingLambdaCombined
がネイティブのラムダより速くなりました。
OK、ラムダコンパイルのパフォーマンスを1桁以上向上させる方法を見つけました。これはヒントです。プロファイラーを実行した後、92% の時間が費やされます。
System.Reflection.Emit.DynamicMethod.CreateDelegate(class System.Type, object)
うーん... なぜ繰り返しごとに新しいデリゲートを作成しているのでしょうか?よくわかりませんが、解決策は別記事で続きます。
どのように解決するのか?
内側のラムダがコンパイルされていない可能性があります! 以下はその証明です。
static void UsingCompiledExpressionWithMethodCall() {
var where = typeof(Enumerable).GetMember("Where").First() as System.Reflection.MethodInfo;
where = where.MakeGenericMethod(typeof(int));
var l = Expression.Parameter(typeof(IEnumerable<int>), "l");
var arg0 = Expression.Parameter(typeof(int), "i");
var lambda0 = Expression.Lambda<Func<int, bool>>(
Expression.Equal(Expression.Modulo(arg0, Expression.Constant(2)),
Expression.Constant(0)), arg0).Compile();
var c1 = Expression.Call(where, l, Expression.Constant(lambda0));
var arg1 = Expression.Parameter(typeof(int), "i");
var lambda1 = Expression.Lambda<Func<int, bool>>(Expression.GreaterThan(arg1, Expression.Constant(5)), arg1).Compile();
var c2 = Expression.Call(where, c1, Expression.Constant(lambda1));
var f = Expression.Lambda<Func<IEnumerable<int>, IEnumerable<int>>>(c2, l);
var c3 = f.Compile();
var t0 = DateTime.Now.Ticks;
for (int j = 1; j < MAX; j++)
{
var sss = c3(x).ToList();
}
var tn = DateTime.Now.Ticks;
Console.WriteLine("Using lambda compiled with MethodCall: {0}", tn - t0);
}
そして、今のタイミングは
Using lambda: 625020
Using lambda compiled: 14687970
Using lambda combined: 468765
Using lambda compiled with MethodCall: 468765
うぉぉぉぉぉぉぉぉぉぉぉぉぉぉぉぉぉぉぉぉぉぉぉぉぉ 速いだけでなく、ネイティブのラムダより速いです。( 頭を掻く ).
もちろん、上記のコードは単に書くのが面倒なだけです。簡単な魔法をかけましょう。
static void UsingCompiledConstantExpressions() {
var f1 = (Func<IEnumerable<int>, IEnumerable<int>>)(l => l.Where(i => i % 2 == 0));
var f2 = (Func<IEnumerable<int>, IEnumerable<int>>)(l => l.Where(i => i > 5));
var argX = Expression.Parameter(typeof(IEnumerable<int>), "x");
var f3 = Expression.Invoke(Expression.Constant(f2), Expression.Invoke(Expression.Constant(f1), argX));
var f = Expression.Lambda<Func<IEnumerable<int>, IEnumerable<int>>>(f3, argX);
var c3 = f.Compile();
var t0 = DateTime.Now.Ticks;
for (int j = 1; j < MAX; j++) {
var sss = c3(x).ToList();
}
var tn = DateTime.Now.Ticks;
Console.WriteLine("Using lambda compiled constant: {0}", tn - t0);
}
VS2010, 最適化オン, デバッグオフの場合です。
Using lambda: 781260
Using lambda compiled: 14687970
Using lambda combined: 468756
Using lambda compiled with MethodCall: 468756
Using lambda compiled constant: 468756
さて、式全体を動的に生成しているわけではなく、呼び出しの連鎖だけだと主張することもできます。しかし、上記の例では、式全体を生成しています。そして、タイミングも一致しています。これは、より少ないコードを書くためのショートカットなのです。
私の理解では、何が起こっているかというと、.Compile()メソッドがコンパイルを内部ラムダに伝搬しないため、定数呼び出しの際に
CreateDelegate
. しかし、これを本当に理解するために、私は.NETの第一人者に、起こっている内部のものについて少しコメントしてもらいたいと思っています。
そして なぜ を、ああ なぜ はネイティブのラムダより速くなったのか!?
関連
-
[解決済み] SQLiteのINSERT/per-secondのパフォーマンスを向上させる
-
[解決済み] 0.1fを0にすると、なぜ10倍もパフォーマンスが落ちるのですか?
-
[解決済み] リスト内包とラムダ+フィルタの比較
-
[解決済み] Swift Betaのパフォーマンス:配列のソート
-
[解決済み] クロージャ」と「ラムダ」の違いは何ですか?
-
[解決済み] Distinct() with lambda?
-
[解決済み] ラムダ(関数)とは何ですか?
-
[解決済み] ラムダ式からプロパティ名を取得する
-
[解決済み】C++11のラムダ式って何?
-
[解決済み] Intel CPU の _mm_popcnt_u64 で、32 ビットのループカウンターを 64 ビットに置き換えると、パフォーマンスが著しく低下します。
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
[解決済み】ASP.NET Core Dependency Injectionのエラーです。アクティブ化しようとしているときに、タイプのサービスを解決できません。
-
[解決済み】Unity3DでOnTriggerEnterが動作しない件
-
[解決済み】非静的メソッドはターゲットを必要とする
-
[解決済み】EF 5 Enable-Migrations : アセンブリにコンテキストタイプが見つかりませんでした
-
[解決済み】"指定されたパスのフォーマットはサポートされていません。"
-
[解決済み】5.7.57 SMTP - MAIL FROMエラー時に匿名メールを送信するためにクライアントが認証されない
-
[解決済み】2年前のMSDateを把握する【クローズド
-
[解決済み] ...基礎となる接続は閉じられました。予期しないエラーが受信で発生しました
-
[解決済み】エラー「必要なフォーマルパラメータに対応する引数が与えられていない」を解決する?
-
[解決済み】名前 'ViewBag' が現在のコンテキストに存在しない - Visual Studio 2015