当前位置:坤哥网-kungge-反射性能漫谈

反射性能漫谈

2017/8/7 15:01:41 kungge阅读(107) 评论(0)

反射影响性能这个话题在网上是一个比较热门的话题了,有人文章谈论反射讨论中就会扯到性能问题,反射使用肯定是会影响性能的,肯定比直接调用要慢,这点毋庸置疑,但是要慢多少呢?这个要看反射使用的次数,次数多的时候性能损耗会很明显,使用次数多了就犹如放大镜一样将性能损耗也放大了。

有人说反射性能很低,使用反射很多时候就是装逼,这其实是大大的误会,上一篇文章《c#知识点梳理之反射》的前面就介绍了反射的作用,真是人云亦云,不去主动了解瞎逼逼,下面通过一个测试例子得出的数据来说明差距,所谓用事实说话。


实际测试


使用了博客园老赵的一个性能计数器CodeTimer,详情参考:《一个简单的性能计数器:CodeTimer
public static class CodeTimer
{
    /// <summary>
    /// 把当前进程及当前线程的优先级设为最高,这样便可以相对减少操作系统在调度上造成的干扰。
    /// 然后调用一次Time方法进行“预热”,让JIT将IL编译成本地代码,让Time方法尽快“进入状态”。
    /// </summary>
    public static void Initialize()
    {
        Process.GetCurrentProcess().PriorityClass = ProcessPriorityClass.High;
        Thread.CurrentThread.Priority = ThreadPriority.Highest;
        long timeElapsed = 0;
        ulong cpuCycles = 0;
        Time("", 1,out timeElapsed,out cpuCycles, () => { });
    }
    /// <summary>
    /// 性能计数
    /// </summary>
    /// <param name="name">名称</param>
    /// <param name="iteration">循环次数</param>
    /// <param name="action">执行的方法体</param>
    public static void Time(string name, int iteration,out long timeElapsed,out ulong cpuCycles,Action action)
    {
        timeElapsed = 0;
        cpuCycles = 0;
        if (String.IsNullOrEmpty(name)) return;
        // 1.保留当前控制台前景色,并使用黄色输出名称参数。
        ConsoleColor currentForeColor = Console.ForegroundColor;
        Console.ForegroundColor = ConsoleColor.Yellow;
        Console.WriteLine(name);
        // 2.强制GC进行收集,并记录目前各代已经收集的次数。
        GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);
        int[] gcCounts = new int[GC.MaxGeneration + 1];
        for (int i = 0; i <= GC.MaxGeneration; i++)
        {
            gcCounts[i] = GC.CollectionCount(i);
        }
        // 3.执行代码,记录下消耗的时间及CPU时钟周期。
        Stopwatch watch = new Stopwatch();
        watch.Start();
        ulong cycleCount = GetCycleCount();
        for (int i = 0; i < iteration; i++) action();
        cpuCycles = GetCycleCount() - cycleCount;
        watch.Stop();
        // 4.恢复控制台默认前景色,并打印出消耗时间及CPU时钟周期。
        timeElapsed = watch.ElapsedMilliseconds;
        Console.ForegroundColor = currentForeColor;
        Console.WriteLine("\tTime Elapsed:\t" + timeElapsed.ToString("N0") + "ms");
        Console.WriteLine("\tCPU Cycles:\t" + cpuCycles.ToString("N0"));
        // 5.打印执行过程中各代垃圾收集回收次数。
        for (int i = 0; i <= GC.MaxGeneration; i++)
        {
            int count = GC.CollectionCount(i) - gcCounts[i];
            Console.WriteLine("\tGen " + i + ": \t\t" + count);
        }
        Console.WriteLine();
    }
    private static ulong GetCycleCount()
    {
        ulong cycleCount = 0;
        QueryThreadCycleTime(GetCurrentThread(), ref cycleCount);
        return cycleCount;
    }
    [DllImport("kernel32.dll")]
    [return: MarshalAs(UnmanagedType.Bool)]
    static extern bool QueryThreadCycleTime(IntPtr threadHandle, ref ulong cycleTime);
    [DllImport("kernel32.dll")]
    static extern IntPtr GetCurrentThread();
}

下面用到的类使用了文章《c#知识点梳理之反射》里面的类,测试的CLR v4.0.30319。


反射类实例


只反射类的实例,然后通过该实例调用方法,完整测试代码如下:

static void Main(string[] args)
{
    int callTimes = 10 * 10000;
    long timeElapsed_Direct = 0;
    ulong cpuCycles_Direct = 0;
    long timeElapsed_Reflect = 0;
    ulong cpuCycles_Reflect = 0;
 
    Console.WriteLine($"调用次数{callTimes}");
    CodeTimer.Time("DirectCall", callTimes,out timeElapsed_Direct, out cpuCycles_Direct, DirectCall);
    CodeTimer.Time("ReflectCall", callTimes,out timeElapsed_Reflect,out cpuCycles_Reflect, ReflectCall);
    Console.WriteLine($"timeElapsed_Reflect/timeElapsed_Direct={timeElapsed_Reflect / timeElapsed_Direct} cpuCycles_Reflect/cpuCycles_Direct={cpuCycles_Reflect / cpuCycles_Direct}");
    Console.ReadKey();
}
static void ReflectCall()
{
    Assembly assembly = Assembly.Load("Senior.DB.SqlServer");
    Type heiperType = assembly.GetType("Senior.DB.SqlServer.SqlServerHelper");
    object helperObject = Activator.CreateInstance(heiperType);
    IDBHelper helper = helperObject as IDBHelper;
    helper.Query();
}
static void DirectCall()
{
    IDBHelper helper = new SqlServerHelper();
    helper.Query();
}

e46895cc-e2be-4804-bb64-e0bf140e3f7b.png
0788b3f9-c773-4bd4-ad6b-5234339724de.png
4ce9e18d-aa7a-423b-97af-ca9364dfce75.png


反射调用方法

   

反射出类实例,然后通过类实例调用GetMethod方法获取要调用的方法的MethodInfo变量,然后使用该变量调用Invoke方法:

static void Main(string[] args)
{
    int callTimes = 10 * 10000;
    long timeElapsed_Direct = 0;
    ulong cpuCycles_Direct = 0;
    long timeElapsed_Reflect = 0;
    ulong cpuCycles_Reflect = 0;
 
    Console.WriteLine($"调用次数{callTimes}");
    CodeTimer.Time("DirectCall", callTimes,out timeElapsed_Direct, out cpuCycles_Direct, DirectCall);
    CodeTimer.Time("ReflectCall", callTimes,out timeElapsed_Reflect,out cpuCycles_Reflect, ReflectCall);
    Console.WriteLine($"timeElapsed_Reflect/timeElapsed_Direct={timeElapsed_Reflect / timeElapsed_Direct} cpuCycles_Reflect/cpuCycles_Direct={cpuCycles_Reflect / cpuCycles_Direct}");
    Console.ReadKey();
}
static void ReflectCall()
{
    Assembly assembly = Assembly.Load("Senior.DB.SqlServer");
    Type functionType = assembly.GetType("Senior.DB.SqlServer.FunctionTest");
    object functionObj = Activator.CreateInstance(functionType);
    MethodInfo method1 = functionType.GetMethod("Function1");
    method1.Invoke(functionObj, null);
}
static void DirectCall()
{
    FunctionTest test = new FunctionTest();
    test.Function1();
}
73a5de90-bd83-461f-968f-a918e41e33d6.png
3da1f662-de76-46a8-b17b-6ded59ea4e14.png
d5037486-169f-49b2-a293-d2cc5516b65b.png


反射获取设置属性


反射出类实例,并给类属性读值赋值

static void Main(string[] args)
{
    int callTimes = 10 * 10000;
    long timeElapsed_Direct = 0;
    ulong cpuCycles_Direct = 0;
    long timeElapsed_Reflect = 0;
    ulong cpuCycles_Reflect = 0;
 
    Console.WriteLine($"调用次数{callTimes}");
    CodeTimer.Time("DirectCall", callTimes,out timeElapsed_Direct, out cpuCycles_Direct, DirectCall);
    CodeTimer.Time("ReflectCall", callTimes,out timeElapsed_Reflect,out cpuCycles_Reflect, ReflectCall);
    Console.WriteLine($"timeElapsed_Reflect/timeElapsed_Direct={timeElapsed_Reflect / timeElapsed_Direct} cpuCycles_Reflect/cpuCycles_Direct={cpuCycles_Reflect / cpuCycles_Direct}");
    Console.ReadKey();
}
static void ReflectCall()
{
    Assembly assembly = Assembly.Load("Model");
    Type userType = assembly.GetType("Model.Users");
    object userModel = Activator.CreateInstance(userType);
    foreach (var item in userType.GetProperties())
    {           
        if (item.Name == "UserName")
        {
            item.SetValue(userModel, "Jack");
        }
        else if (item.Name == "Age")
        {
            item.SetValue(userModel, 30);
        }
        else if (item.Name == "CreateTime")
        {
            item.SetValue(userModel, DateTime.Now);
        }
    }
}
static void DirectCall()
{
    Users user = new Users();
    user.UserName = "Jack";
    user.Age = 30;
    user.CreateTime = DateTime.Now;
}
8560f75f-718f-49f4-b360-1d755afd2b6e.png
2b8d61c5-6eb8-4fbd-836a-29b0d130dc1b.png754f51f0-f2d8-4d20-bbac-7d9abb7f5cf9.png


以上测试只是随机测试几次,并不能很准确的测试出差距,但是大致范围还是可以表现出来的。

测试反射属性的时候,属性值很少,实际情况属性会很多,这也会拉大差距。
由上面测试数据总结下:

1.反射次数数量级达到100000时,差距明显。

2.反射调用方法Invoke差距特别大,开发时候应考虑下。

3.总的来说反射带来的性能损耗还可以,不考虑Invoke情况下和普通调用的差距都控制在100倍以内。据说在.net 2.0环境下测试差距在200倍,这个没有测试过,有兴趣的可以测试下。 随着.net版本升级微软估计对反射进行了一些优化,毕竟反射还是经常用的,不是有句话叫“反射反射程序员的快乐”嘛。

说了这么多那反射到底要不要使用了,这个要看实际的情况,若是系统对性能要求不严格的话,为了开发效率可以使用反射牺牲一些性能损耗。如果反射调用次数很少,那么这些性能完全可以忽略,可以大胆使用;如果系统需要大量反射,那么应该考虑是否使用, 若是要使用的话,可以考虑将反射缓存起来。

在实际项目中,需要用的时候还是建议使用,又不是每个系统都像淘宝、京东那样对性能要求很高的,大部分的项目使用反射带来的性能影响还是很小。特别是小项目,先以实现功能为主,不要太在意性能了,应把关注点放在耦合性、扩展性和可维护性这些方面。当明显感觉性能差的时候再去优化,比如缓存反射等方法,若是反射还是会有影响,系统设计好的话,完全可以很容易的用其他方式代替的。


反射执行方法与表达式执行方法差异


上面反射方式调用方法相对来说性能损耗严重,微软提供了表达式调用方法的方式,可以大大降低降低性能损耗。
博客园老蒋有篇文章介绍二者之间差异,详情移步《比较一下以“反射”和“表达式”执行方法的性能差异 》
这里引用他写的方法来做下比较:
class Program
{
    public static MethodInfo Method { get; private set; }
    public static FunctionTest Target { get; private set; }
    public static Action<FunctionTest> Executor { get; private set; }
    private static Action<FunctionTest> CreateExecutor(MethodInfo method)
    {
        ParameterExpression target = Expression.Parameter(typeof(FunctionTest), "target");
        Expression expression = Expression.Call(target, method);
        return Expression.Lambda<Action<FunctionTest>>(expression, target).Compile();
    }
    static Program()
    {
        Assembly assembly = Assembly.Load("Senior.DB.SqlServer");
        Type functionType = assembly.GetType("Senior.DB.SqlServer.FunctionTest");
        object functionObj = Activator.CreateInstance(functionType);
        Target = functionObj as FunctionTest;
        Method = functionType.GetMethod("Function1");
        Executor = CreateExecutor(Method);
    }
    static void Main(string[] args)
    {
        int callTimes = 10 * 10000;
        long timeElapsed_Expression = 0;
        ulong cpuCycles_Expression = 0;
        long timeElapsed_Reflect = 0;
        ulong cpuCycles_Reflect = 0;
        Console.WriteLine($"调用次数{callTimes}");
        CodeTimer.Time("ExpressionCall", callTimes, out timeElapsed_Expression, out cpuCycles_Expression, ExpressionCall);
        CodeTimer.Time("ReflectCall", callTimes, out timeElapsed_Reflect, out cpuCycles_Reflect, ReflectCall);
        Console.WriteLine($"timeElapsed_Reflect/timeElapsed_Expression={timeElapsed_Reflect / timeElapsed_Expression} cpuCycles_Reflect/cpuCycles_Expression={cpuCycles_Reflect / cpuCycles_Expression}");
        Console.ReadKey();
    }
    static void ReflectCall()
    {
        Method.Invoke(Target,null);
    }
    static void ExpressionCall()
    {
        Executor(Target);
    }
}
一万次Expression执行时间太短了几乎是0ms,会报错除以0的错误,就10W此以上测试:
9406596a-545c-4ddd-8312-156754de5c83.png
652da468-c4f5-463b-8bc2-fa2a041819c3.png
总体来说Expression方式执行方法比Invoke方式执行方法快5~10倍,已经是很明显的进步了,虽然和普通调用还是存在量级差距,但在反射使用调用方法这块就考虑使用Expression方式代替也是一种不错的选择。


反射性能

发表评论 没有账号,注册评论

博主

  • 用户名:kungge
  • 昵称:kungge

文章标签