亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 編程 > C# > 正文

C#中方法的直接調用、反射調用與Lambda表達式調用對比

2020-01-24 01:48:39
字體:
來源:轉載
供稿:網友

想調用一個方法很容易,直接代碼調用就行,這人人都會。其次呢,還可以使用反射。不過通過反射調用的性能會遠遠低于直接調用――至少從絕對時間上來看的確是這樣。雖然這是個眾所周知的現象,我們還是來寫個程序來驗證一下。比如我們現在新建一個Console應用程序,編寫一個最簡單的Call方法。

復制代碼 代碼如下:

class Program
{
    static void Main(string[] args)
    {
       
    }

    public void Call(object o1, object o2, object o3) { }
}


Call方法接受三個object參數卻沒有任何實現,這樣我們就可以讓測試專注于方法調用,而并非方法實現本身。于是我們開始編寫測試代碼,比較一下方法的直接調用與反射調用的性能差距:
復制代碼 代碼如下:

static void Main(string[] args)
{
    int times = 1000000;
    Program program = new Program();
    object[] parameters = new object[] { new object(), new object(), new object() };
    program.Call(null, null, null); // force JIT-compile

    Stopwatch watch1 = new Stopwatch();
    watch1.Start();
    for (int i = 0; i < times; i++)
    {
        program.Call(parameters[0], parameters[1], parameters[2]);
    }
    watch1.Stop();
    Console.WriteLine(watch1.Elapsed + " (Directly invoke)");

    MethodInfo methodInfo = typeof(Program).GetMethod("Call");
    Stopwatch watch2 = new Stopwatch();
    watch2.Start();
    for (int i = 0; i < times; i++)
    {
        methodInfo.Invoke(program, parameters);
    }
    watch2.Stop();
    Console.WriteLine(watch2.Elapsed + " (Reflection invoke)");

    Console.WriteLine("Press any key to continue...");
    Console.ReadKey();
}


執行結果如下:
復制代碼 代碼如下:

00:00:00.0119041 (Directly invoke)
00:00:04.5527141 (Reflection invoke)
Press any key to continue...

通過各調用一百萬次所花時間來看,兩者在性能上具有數量級的差距。因此,很多框架在必須利用到反射的場景中,都會設法使用一些較高級的替代方案來改善性能。例如,使用CodeDom生成代碼并動態編譯,或者使用Emit來直接編寫IL。不過自從.NET 3.5發布了Expression相關的新特性,我們在以上的情況下又有了更方便并直觀的解決方案。

了解Expression相關特性的朋友可能知道,System.Linq.Expressions.Expression<TDelegate>類型的對象在調用了它了Compile方法之后將得到一個TDelegate類型的委托對象,而調用一個委托對象與直接調用一個方法的性能開銷相差無幾。那么對于上面的情況,我們又該得到什么樣的Delegate對象呢?為了使解決方案足夠通用,我們必須將各種簽名的方法統一至同樣的委托類型中,如下:

復制代碼 代碼如下:

public Func<object, object[], object> GetVoidDelegate()
{
    Expression<Action<object, object[]>> exp = (instance, parameters) =>
        ((Program)instance).Call(parameters[0], parameters[1], parameters[2]);

    Action<object, object[]> action = exp.Compile();
    return (instance, parameters) =>
    {
        action(instance, parameters);
        return null;
    };
}


如上,我們就得到了一個Func<object, object[], object>類型的委托,這意味它接受一個object類型與object[]類型的參數,以及返回一個object類型的結果――等等,朋友們有沒有發現,這個簽名與MethodInfo類型的Invoke方法完全一致?不過可喜可賀的是,我們現在調用這個委托的性能遠高于通過反射來調用了。那么對于有返回值的方法呢?那構造一個委托對象就更方便了:
復制代碼 代碼如下:

public int Call(object o1, object o2) { return 0; }

public Func<object, object[], object> GetDelegate()
{
    Expression<Func<object, object[], object>> exp = (instance, parameters) =>
        ((Program)instance).Call(parameters[0], parameters[1]);

    return exp.Compile();
}


至此,我想朋友們也已經能夠輕松得出調用靜態方法的委托構造方式了??梢?,這個解決方案的關鍵在于構造一個合適的Expression<TDelegate>,那么我們現在就來編寫一個DynamicExecuter類來作為一個較為完整的解決方案:

復制代碼 代碼如下:

public class DynamicMethodExecutor
{
    private Func<object, object[], object> m_execute;

    public DynamicMethodExecutor(MethodInfo methodInfo)
    {
        this.m_execute = this.GetExecuteDelegate(methodInfo);
    }

    public object Execute(object instance, object[] parameters)
    {
        return this.m_execute(instance, parameters);
    }

    private Func<object, object[], object> GetExecuteDelegate(MethodInfo methodInfo)
    {
        // parameters to execute
        ParameterExpression instanceParameter =
            Expression.Parameter(typeof(object), "instance");
        ParameterExpression parametersParameter =
            Expression.Parameter(typeof(object[]), "parameters");

        // build parameter list
        List<Expression> parameterExpressions = new List<Expression>();
        ParameterInfo[] paramInfos = methodInfo.GetParameters();
        for (int i = 0; i < paramInfos.Length; i++)
        {
            // (Ti)parameters[i]
            BinaryExpression valueObj = Expression.ArrayIndex(
                parametersParameter, Expression.Constant(i));
            UnaryExpression valueCast = Expression.Convert(
                valueObj, paramInfos[i].ParameterType);

            parameterExpressions.Add(valueCast);
        }

        // non-instance for static method, or ((TInstance)instance)
        Expression instanceCast = methodInfo.IsStatic ? null :
            Expression.Convert(instanceParameter, methodInfo.ReflectedType);

        // static invoke or ((TInstance)instance).Method
        MethodCallExpression methodCall = Expression.Call(
            instanceCast, methodInfo, parameterExpressions);
       
        // ((TInstance)instance).Method((T0)parameters[0], (T1)parameters[1], ...)
        if (methodCall.Type == typeof(void))
        {
            Expression<Action<object, object[]>> lambda =
                Expression.Lambda<Action<object, object[]>>(
                    methodCall, instanceParameter, parametersParameter);

            Action<object, object[]> execute = lambda.Compile();
            return (instance, parameters) =>
            {
                execute(instance, parameters);
                return null;
            };
        }
        else
        {
            UnaryExpression castMethodCall = Expression.Convert(
                methodCall, typeof(object));
            Expression<Func<object, object[], object>> lambda =
                Expression.Lambda<Func<object, object[], object>>(
                    castMethodCall, instanceParameter, parametersParameter);

            return lambda.Compile();
        }
    }
}

DynamicMethodExecutor的關鍵就在于GetExecuteDelegate方法中構造Expression Tree的邏輯。如果您對于一個Expression Tree的結構不太了解的話,不妨嘗試一下使用Expression Tree Visualizer 來對一個現成的Expression Tree進行觀察和分析。我們將一個MethodInfo對象傳入DynamicMethodExecutor的構造函數之后,就能將各組不同的實例對象和參數對象數組傳入Execute進行執行。這一切就像使用反射來進行調用一般,不過它的性能就有了明顯的提高。例如我們添加更多的測試代碼:

復制代碼 代碼如下:

DynamicMethodExecutor executor = new DynamicMethodExecutor(methodInfo);
Stopwatch watch3 = new Stopwatch();
watch3.Start();
for (int i = 0; i < times; i++)
{
    executor.Execute(program, parameters);
}
watch3.Stop();
Console.WriteLine(watch3.Elapsed + " (Dynamic executor)");

現在的執行結果則是:

復制代碼 代碼如下:

00:00:00.0125539 (Directly invoke)
00:00:04.5349626 (Reflection invoke)
00:00:00.0322555 (Dynamic executor)
Press any key to continue...

事實上,Expression<TDelegate>類型的Compile方法正是使用Emit來生成委托對象。不過現在我們已經無需將目光放在更低端的IL上,只要使用高端的API來進行Expression Tree的構造,這無疑是一種進步。不過這種方法也有一定局限性,例如我們只能對公有方法進行調用,并且包含out/ref參數的方法,或者除了方法外的其他類型成員,我們就無法如上例般愜意地編寫代碼了。

補充

木野狐兄在評論中引用了Code Project的文章《A General Fast Method Invoker》,其中通過Emit構建了FastInvokeHandler委托對象(其簽名與Func<object, object[], object>完全相同)的調用效率似乎較“方法直接”調用的性能更高(雖然從原文示例看來并非如此)。事實上FastInvokeHandler其內部實現與DynamicMethodExecutor完全相同,居然有如此令人不可思議的表現實在讓人嘖嘖稱奇。我猜測,FastInvokeHandler與DynamicMethodExecutor的性能優勢可能體現在以下幾個方面:

1.范型委托類型的執行性能較非范型委托類型略低(求證)。
2.多了一次Execute方法調用,損失部分性能。
3.生成的IL代碼更為短小緊湊。
4.木野狐兄沒有使用Release模式編譯。:P

不知道是否有對此感興趣的朋友能夠再做一個測試,不過請注意此類性能測試一定需要在Release編譯下進行(這點很容易被忽視),否則意義其實不大。

此外,我還想強調的就是,本篇文章進行是純技術上的比較,并非在引導大家追求點滴性能上的優化。有時候看到一些關于比較for或foreach性能優劣的文章讓許多朋友都糾結與此,甚至搞得面紅耳赤,我總會覺得有些無可奈何。其實從理論上來說,提高性能的方式有許許多多,記得當時在大學里學習Introduction to Computer System這門課時得一個作業就是為一段C程序作性能優化,當時用到不少手段,例如內聯方法調用以減少CPU指令調用次數、調整循環嵌套順序以提高CPU緩存命中率,將一些代碼使用內嵌ASM替換等等,可謂“無所不用其極”,大家都在為幾個時鐘周期的性能提高而發奮圖強歡呼雀躍……

那是理論,是在學習。但是在實際運用中,我們還必須正確對待學到的理論知識。我經常說的一句話是:“任何應用程序都會有其性能瓶頸,只有從性能瓶頸著手才能做到事半功倍的結果。”例如,普通Web應用的性能瓶頸往往在外部IO(尤其是數據庫讀寫),要真正提高性能必須從此入手(例如數據庫調優,更好的緩存設計)。正因如此,開發一個高性能的Web應用程序的關鍵不會在語言或語言運行環境上,.NET、RoR、PHP、Java等等在這一領域都表現良好。

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
欧美日本在线视频中文字字幕| 91精品视频在线| 欧美日韩国产第一页| 国产精品免费电影| 97国产真实伦对白精彩视频8| 欧美大学生性色视频| 欧美日韩激情视频| 青草成人免费视频| 奇门遁甲1982国语版免费观看高清| 精品久久久久久久大神国产| 久久久精品国产网站| 国产69久久精品成人看| 国产一区二区三区在线观看视频| 国产精品2018| 成人黄色大片在线免费观看| 亚洲日本成人网| 国产精品视频地址| 欧美激情视频在线免费观看 欧美视频免费一| 久久久亚洲影院| 欧美日韩国产页| 国产视频精品一区二区三区| 亚洲最大福利网| 欧美大学生性色视频| 久久九九国产精品怡红院| 国产97色在线|日韩| 欧美性生交xxxxx久久久| 亚洲视屏在线播放| 国产精品99久久久久久人| 久久97精品久久久久久久不卡| 国产成人精品一区二区在线| 亚洲综合中文字幕在线观看| 日韩在线观看免费高清完整版| 日韩成人激情视频| 久久久久久久久久久人体| 亚洲精美色品网站| 成人免费大片黄在线播放| 一区二区三区视频免费| 亚洲成人免费网站| 国产人妖伪娘一区91| 一区二区三区美女xx视频| 成人97在线观看视频| 亚洲人成网站777色婷婷| 久久精品成人欧美大片| 日韩在线视频观看正片免费网站| 国产z一区二区三区| 精品一区二区亚洲| 91国内免费在线视频| 亚洲精品大尺度| 亚洲欧美在线x视频| 久久久久久香蕉网| 欧美高清视频一区二区| 中文字幕欧美精品日韩中文字幕| 欧美精品日韩三级| 成人av电影天堂| 国产日韩欧美视频| 国产精品 欧美在线| 欧美区二区三区| 国产精品国产自产拍高清av水多| 亚洲国语精品自产拍在线观看| 成年无码av片在线| 亚洲国产精久久久久久久| 中文字幕日韩免费视频| 日韩国产欧美精品在线| 国内精品美女av在线播放| 欧美壮男野外gaytube| 亚洲一区二区三区香蕉| 欧美成人精品在线视频| 91免费综合在线| 成人性教育视频在线观看| 成人中文字幕在线观看| 日韩av不卡电影| 欧美久久精品一级黑人c片| 欧美老女人性视频| 精品国产福利视频| 亚洲自拍中文字幕| 国产精品久久久精品| 亚洲最大福利视频网站| 欧美一区二粉嫩精品国产一线天| 日韩在线视频一区| 隔壁老王国产在线精品| 欧美日韩国产在线播放| 欧美一级片一区| 91精品国产99久久久久久| 国产精品小说在线| 国产欧美日韩精品丝袜高跟鞋| 欧美午夜电影在线| 久久精品99无色码中文字幕| 欧亚精品中文字幕| 久久男人av资源网站| 国产视频亚洲精品| 97avcom| xxav国产精品美女主播| 久久精品中文字幕电影| 91大神福利视频在线| 亚洲色图综合久久| 国产欧美精品一区二区三区-老狼| 国产精品香蕉av| 日韩免费精品视频| 国产精品久久久久久超碰| 国产91色在线播放| 在线亚洲欧美视频| 亚洲xxxx做受欧美| 国产综合久久久久| 中文字幕精品www乱入免费视频| 国产精品久久久久久久天堂| 在线观看视频亚洲| 国产精品丝袜白浆摸在线| 久久夜色精品国产亚洲aⅴ| 久久欧美在线电影| 亚洲夜晚福利在线观看| 久久亚洲影音av资源网| 精品视频久久久久久久| 高清亚洲成在人网站天堂| 国产成人综合久久| 亚洲石原莉奈一区二区在线观看| 亚洲精品福利免费在线观看| 主播福利视频一区| 国产偷国产偷亚洲清高网站| 国产精品视频免费在线观看| 欧美高清在线视频观看不卡| 亚洲欧美国产高清va在线播| 国产精品6699| 久久久久国产视频| 日韩高清a**址| 国产日韩欧美黄色| 亚洲视频999| 欧美国产日本在线| 日韩国产高清视频在线| 精品一区二区亚洲| 精品精品国产国产自在线| 国产一区二区三区视频在线观看| 亚洲成人网av| 精品亚洲一区二区| 亚洲色图18p| 日韩欧美在线一区| 欧美有码在线观看视频| 91九色视频在线| 亚洲男人天天操| 日韩人在线观看| 久久精品一本久久99精品| 欧美激情xxxx| 国产va免费精品高清在线观看| 91在线免费观看网站| 日韩在线播放av| 亚洲综合小说区| 亚洲第一在线视频| 亚洲精品一区久久久久久| 精品久久久久久中文字幕大豆网| 少妇激情综合网| 亚洲欧美中文字幕在线一区| 久久国产一区二区三区| 成人精品网站在线观看| 欧美高清激情视频| 欧美黑人性生活视频| 最近2019中文字幕在线高清| 亚洲欧美日韩在线一区| 久久全球大尺度高清视频| 国产一区二区三区毛片| 中文字幕亚洲欧美日韩高清| www亚洲精品| 97国产真实伦对白精彩视频8| 成人444kkkk在线观看| 国产精品∨欧美精品v日韩精品| 国产噜噜噜噜久久久久久久久|