式ツリーによるURL構築のパフォーマンスの最適化

8855 ワード

式ツリーを介してURLを構築する方法を引き続き改善します.前の記事では、アクセシビリティはActionNameAttributeを正しく認識していましたが、今回はパフォーマンスの問題が改善されました.まず、式ツリーからRouteValueDictionaryを取得する方法を見てみましょう.
public static RouteValueDictionary GetRouteValues(
    Expression<Action> action)
    where TController : Controller
{
    ...

    var rvd = new RouteValueDictionary();
    rvd.Add("controller", controllerName);
    rvd.Add("action", GetActionName(call.Method));

    AddParameterValues(rvd, call);
    return rvd;
}

private static void AddParameterValues(
    RouteValueDictionary rvd, MethodCallExpression call)
{
    ParameterInfo[] parameters = call.Method.GetParameters();

    for (int i = 0; i < parameters.Length; i++)
    {
        Expression arg = call.Arguments[i];

        object value = null;
        ConstantExpression ce = arg as ConstantExpression;
        if (ce != null)
        {
            // If argument is a constant expression, just get the value
            value = ce.Value;
        }
        else
        {
            // Otherwise, convert the argument subexpression to type object,
            // make a lambda out of it, compile it, and invoke it to get the value
            Expression<Func<object>> lambdaExpression = 
                Expression.Lambda<Func<object>>(
                    Expression.Convert(arg, typeof(object)));
            Func<object> func = lambdaExpression.Compile();
            value = func();
        }

        rvd.Add(parameters[i].Name, value);
    }
}

今回注目したのは2つ目の方法AddParameterValueです.このメソッドの目的は、Action呼び出しを表す式ツリー(MethodCallExpression)からすべてのパラメータを抽出することです.式ツリーでもあり、RouteValueDictionaryに表示される値を入力します.このコードは、「LambdaExpressionオブジェクトを使用してカプセル化し、再コンパイルし、最後に実行する」という従来の式ツリーを使用して、Expressionオブジェクトの値を取得します.しかし,Compileメソッドの性能は比較的低く,密に実行すると性能に一定の影響を及ぼす.
では、ASP.NET MVCのシーンで、Compileメソッドの実行頻度はどう思いますか?このようなシーンを想像してみてください.
<h2>Article Listh2>
 
 foreach (var article in Model.Articles) { %>
<div>
    = Html.ActionLink<ArticleController>(
            c => c.Detail(article.ArticleID, 1), article.Title) %>
    
     for (var page = 2; page <= article.MaxPage; page++) { %>
    <small>
        = Html.ActionLink<ArticleController>(
            c => c.Detail(article.ArticleID, page), page.ToString()) %>
    small>
     } %>        
div>
 } %>

上記のコードの役割は,文章リストページに一連の文章詳細ページへのリンクを生成することである.では、上記のコードでは、式ツリーの計算が何回行われるのでしょうか.
Html.ActionLink<ArticleController>(
    c => c.Detail(article.ArticleID, 1), article.Title)
 
Html.ActionLink<ArticleController>(
    c => c.Detail(article.ArticleID, page), article.Title)

各記事について(2*MaxPage–1)回の計算が行われ、数十件の記事を持つリストページについては、計算回数が100回を超える可能性が高いことがわかります.また,分類リスト,Tag Cloudなど,ページ上の様々な要素を加えると,やや複雑なページを生成するたびに数百回の式ツリー計算が行われる.Simone Chiarettaのパフォーマンステストでは、エクスプレッションツリーを使用してリンクを生成するのにかかる時間は、文字列を直接使用するのに約30倍です.私のローカルテストの結果、P 4.0 GHzのサーバでは、単純な4つの演算式を1万個連続で計算するのに1秒以上かかります.これは無視できるパフォーマンスオーバーヘッドではなく、よりパフォーマンスの良い式ツリー計算方法を導入する必要があります.
「エクスプレッションツリーの高速計算」では、このトピックについて非常に深く検討し(エクスプレッションツリーのキャッシュという一連の文章を計算することもできます)、最終的にはFastLambdaという直接多重化可能なソリューションを提供しました.したがって、ここでは、式ツリーの計算方法を改善するには、次の方法を使用します.
private static void AddParameterValues(
    RouteValueDictionary rvd, MethodCallExpression call)
{
    ParameterInfo[] parameters = call.Method.GetParameters();

    for (int i = 0; i < parameters.Length; i++)
    {
        rvd.Add(parameters[i].Name, Eval(call.Arguments[i]));
    }
}

private static FastEvaluator s_parameterEvaluator = new FastEvaluator();

private static object Eval(Expression exp)
{
    ConstantExpression ce = exp as ConstantExpression;
    if (ce != null) return ce.Value;

    return s_parameterEvaluator.Eval(exp);
}

FastEvaluatorはFastLambdaプロジェクトで提供されるクラスで、入力された式ツリーを分析し、その構造に基づいてコンパイルされた結果をキャッシュするので、次回同じ構造の式を入力すると、コンパイルのオーバーヘッドを省くことができます.たとえば、上記の例では、articleがどのオブジェクトを指しているかにかかわらず、pageの値がいくらであるかにかかわらず、article.ArticleIDまたはpage自体の構造は常に変化しません.したがって,先の例では,ページに何回アクセスしても,何回ループしても,2回のコンパイルしか行わない.また、FastEvaluatorはスレッドの安全なコンポーネントとして実装されているため、ここでは直接使用するだけで、あまり考慮する必要はありません.
では、これはどのくらいの性能向上をもたらすのでしょうか.次の図面を参照してください.
この図は2 n+1ノードを持つ式ツリーを計算する際の一般的なやり方(赤い線)とFastEvaluator(紫線)の性能差を表している.私の個人的な経験によると、プロジェクトで計算される式ツリーのノード数は一般的に10個以内です.図に示すように、このデータ範囲では、FastEvaluatorの計算にかかる時間は、従来の方法の1/20にすぎない.先の例では、ノード数が3または5である場合、nは1または3であることを示し、このようなノード数が極めて少ない場合、性能の差は50〜100倍に達することができる.
もちろん、1つのアプリケーションのパフォーマンスは、このような簡単な詳細によって決定されるわけではありませんが、頻繁な操作のために非常に明らかなパフォーマンス最適化が行われています.さらに重要なのは、使いやすさを損なったわけではありません.したがって、ASP.NET MVCでエクスプレッションツリーでURLを構築する場合は、現在のシナリオを使用して改善することをお勧めします.
FastEvaluatorコンポーネントの原理や実装などの詳細については、私が書いた「高速計算式ツリー」を参照してください.