技术经验解读:【c#】反射真的很可怕吗?

简介: 技术经验解读:【c#】反射真的很可怕吗?

说起c#中的反射,相信很多人第一反应就是“性能低”,或者是"慢"。当一个人说你有问题,那可能是说你有问题的那个人自己有问题,但如果N多人说你有问题,那估计真的是你有问题,所以,我从来不争论,也不否认,也不怀疑反射比起直接调用性能要低。直接调用的代码是被编译好,按部就班执行就行了,但反射调用过程被推迟到运行期,是动态的,而很多情况下,动态就意味着性能的损失。有时反射还意味着动态加载(Assambly.LoadFrom),就不免要发生IO操作,那更是慢上加慢。


可能因为反射慢这个事实,而事实经常被道听途说,甚至有些东西被以讹传讹后,后来竟然出现“万恶的反射”和“反射不可接受”等等态度。但是真的是不可接受吗?asp.net或者IIS中就有很多东西是反射加载的,例如iis中N多的http module。我想搞清楚反射为什么慢,背后到底发生了什么事情,于是google很不少资料,其中在stackoverflow上也提问过(在这里),其中有些人的意见是:”反射慢?看跟谁比较了;反射是慢,但对我们来说足以够快。“于是,我有了写这篇博客的念头。


我的态度就是:首先,没错,反射是慢,但这是因为我们拿它跟直接调用做比较,反射并不是不可接受,只是因为直接调用太快了。php单从执行效率来说比c#慢多了,但谁也不能断定php比asp.net差,java很多地方也没有c#效率高,但who cares?大多时候我们不是做实时或者对性能非常高的系统(就算做也不会拿c#做)。开发着中小型的web系统,然后叫嚷着”别用反射,性能不行“,那不是扯吗?


其次,不要因为反射速度比不上直接调用就将它看成毒物,没有了反射,工厂方法怎么办?运行期加载怎么解决?因为没有反射,做出来的系统有可能会因为另外开发原本反射能实现的功能而变得更慢,更难维护。我们不应该因为锤子不能切菜就将锤子骂得一无是处,处处皆不用锤子,要知道,用菜刀来钉水泥钉是很痛苦的。


接着我们来看看反射为什么慢。除了在stackoverflow上那篇 Why is the performance of reflection in C# poor?中别人的回答外,我也看了firelong抨击反射的文章《C#会重蹈覆辙吗?系列之2:反射及元数据的性能问题》和微软CLR程序经理Joel Pobar写的《Dodge Common Performance Pitfalls to Craft Speedy Applications》,都是不错的文章,虽然firelong是持抨击态度,但他说的事实的确可以拿来参考。那么,反射之所以慢,是因为以下几点(照抄firelong的,因为我也是看了他的才知道):


首先要经过一个绑定过程,非常耗时(用字符串名称和metadata里面的字符串进行比对,字符串查找的算法大家都知道是很慢的操作)。


然后要进行参数个数、类型等的校验;如果不匹配还要搜索可能的类型转换。


进行CAS代码访问安全的验证,看允不允许调用。


以上几个工作,如果不用反射应该是由C#编译器负责在编译时检查的。但是现在如果用反射,全都放到了运行时检查。


这其中会产生一大堆的临时对象(比如MemberInfo Cache),给垃圾收集器造成巨大负担。


如果有兴趣,可以去查找更多资料。


  再接下来就是,我并不认为反射仅因为这个性能问题就变得一无是处,很多时候不必关心他,对我而言,花时间在优化反射身上,还不如借助反射的特性,而将这些时间花在数据库优化上。


于是,我编写了一些代码来测试一下反射的速度(与直接调用作为对比)。但是对于这样的代码,首先要明确一点就是:肯定不能充分说明问题,因为这些代码只能称为代码片段(code snippet),并不是实际的生产环境的测试结果,而且,由于我对c#很多底层的机制不了解,有可能导致产生误解。所以代码只能作为参考。


我的控制台程序Main方法代码:


static void Main(string【】 args)


{


//先设置进程和线程的在高优先级,排除调度的影响


Process.GetCurrentProcess().PriorityClass = ProcessPriorityClass.High;


Thread.CurrentThread.Priority = ThreadPriority.Highest;


int iteraction = 10001000;


Testing instance = new Testing();


Type testType = typeof(reflection_performance_testing.Testing);


Stopwatch stw = new Stopwatch();


/----------------------------------------------------------------------------------------------------/


ulong cycleStart_dir_new = 0;


ulong cycleStop_dir_new = 0;


QueryThreadCycleTime(GetCurrentThread(), ref cycleStart_dir_new);


stw.Start();


for (int i = 0; i < iteraction; i++)


{


Testing t = new Testing();


}


stw.Stop();


QueryThreadCycleTime(GetCurrentThread(), ref cycleStop_dir_new);


Console.Write("直接实例化 {0} 个实例总共花费 {1} 毫秒 和 {2} cpu 周期 \n\n\n\n", iteraction, stw.ElapsedMilliseconds, cycleStop_dir_new - cycleStart_dir_new);


/----------------------------------------------------------------------------------------------------/


/----------------------------------------------------------------------------------------------------/


ulong cycleStart_ref_act = 0;


ulong cycleStop_ref_act = 0;


QueryThreadCycleTime(GetCurrentThread(), ref cycleStart_ref_act);


stw.Reset();


stw.Start();


for (int i = 0; i < iteraction; i++)


{


Testing t = (Testing)Activator.CreateInstance(testType);


}


stw.Stop();


QueryThreadCycleTime(GetCurrentThread(), ref cycleStop_ref_act);


Console.Write("重复反射创建(Activator方式)总共花费 {0} 毫秒 和 {1} cpu 周期\n\n\n", stw.ElapsedMilliseconds, cycleStart_ref_act - cycleStop_ref_act);


/----------------------------------------------------------------------------------------------------/


/----------------------------------------------------------------------------------------------------/


ulong cycleStart_ref_get = 0;


ulong cycleStop_ref_get = 0;


QueryThreadCycleTime(GetCurrentThread(), ref cycleStart_ref_get);


stw.Reset();


stw.Start();


for (int i = 0; i < iteraction; i++)


{


Testing t = (Testing)testType.GetConstructors()【0】.Invoke(null);


}


stw.Stop();


QueryThreadCycleTime(GetCurrentThread(), ref cycleStop_ref_get);


Console.Write("重复反射(GetConstructor方式)创建 {0} 个实例总共花费 {1} 毫秒 和 {2} cpu 周期 \n\n\n\n", iteraction, stw.ElapsedMilliseconds, cycleStop_ref_get - cycleStart_ref_get);


/----------------------------------------------------------------------------------------------------/


/----------------------------------------------------------------------------------------------------/


ulong cycleStart_ref_load = 0;


ulong cycleStop_ref_load = 0;


QueryThreadCycleTime(GetCurrentThread(), ref cycleStart_ref_load);


stw.Reset();


stw.Start();


for (int i = 0; i < iteraction; i++)


{


Assembly ass = Assembly.LoadFrom("AssTesting.dll");


AssTesting.AssTestingClass t = (AssTesting.AssTestingClass)ass.CreateInstance("AssTesting.AssTestingClass");


}


stw.Stop();


QueryThreadCycleTime(GetCurrentThread(), ref cycleStop_ref_load);


Console.Write("重复反射创建(Load Assambly方式) {0} 个实例总共花费 {1} 毫秒 和 {2} cpu 周期 \n\n\n\n", iteraction, stw.ElapsedMilliseconds, cycleStop_ref_load - cycleStart_ref_load);


/----------------------------------------------------------------------------------------------------/


/----------------------------------------------------------------------------------------------------/


ulong cycleStart_dir_invoke = 0;


ulong cycleStop_dir_invoke = 0;


QueryThreadCycleTime(GetCurrentThread(), ref cycleStart_dir_invoke);


stw.Reset();


stw.Start();


for (int i = 0; i < iteraction; i++)


{


instance.DoSomething();


}


stw.Stop();


QueryThreadCycleTime(GetCurrentThread(), ref cycleStop_dir_invoke);


Console.Write("重复 {0} 次直接调用方法总共花费 {1} 毫秒 和 {1} cpu 周期 \n\n\n\n", iteraction, stw.ElapsedMilliseconds, cycleStop_dir_invoke - cycleStart_dir_invoke);


/----------------------------------------------------------------------------------------------------/


/----------------------------------------------------------------------------------------------------/


ulong cycleStart_ref_invoke = 0;


ulong cycleStop_ref_invoke = 0;


QueryThreadCycleTime(GetCurrentThread(), ref cycleStart_ref_invoke);


stw.Reset();


stw.Start();


for (int i = 0; i < iteraction; i++)


{


testType.GetMethod("DoSomething").Invoke(instance, null);


}


stw.Stop();


QueryThreadCycleTime(GetCurrentThread(), ref cycleStop_ref_invoke);


Console.Write("重复反射调用方法 {0} 次总共花费 {1} 毫秒 和 {2} cpu 周期 \n\n\n\n", iteraction, stw.ElapsedMilliseconds, cycleStop_ref_invoke - cycleStart_ref_invoke);


/----------------------------------------------------------------------------------------------------*/


Console.Write("全部完毕");


Console.Read();


}


其中的Testing类如下:


View Code


class Testing


{


public Testing()


{ }


public Testing(int i)


{ }


public Testing(string s)


{ }//代码效果参考:http://hnjlyzjd.com/hw/wz_24439.html


public Testing(bool b)


{ }


public Testing(int i, string s)


{ }


public Testing(int i, string s, bool b)


{ }


public void DoSomething()


{


//do nothing here..


}


public void Method1() { }


public void Method2() { }


public void Method3() { }


public void Method4() { }


public //代码效果参考:http://hnjlyzjd.com/xl/wz_24445.html

void Method5() { }

public void Method6() { }


public void Method7() { }


public void Method8() { }


public void Method9() { }


public void Method10() { }


public void Method11() { }


public void Method12() { }


public void Method13() { }


public void Method14() { }


public void Method15() { }


public void Method16() { }


public void Method17() { }


public void Method18() { }


public void Method19() { }


public void Method20() { }


private int a1;


private int a2;


private int a3;


private int a4;


private int a5;


private int a6;


private int a7;


private int a8;


private int a9;


private int a10;


private int a11;


private int a12;


public int A1


{


get { return a1; }


set { a1 = value; }


}


public int A2


{


get { return a2; }


set { a2 = value; }


}


public int A3


{


get { return a3; }


set { a3 = value; }


}


public int A4


{


get { return a4; }


set { a4 = value; }


}


public int A5


{


get { return a5; }


set { a5 = value; }


}


public int A6


{


get { return a6; }


set { a6 = value; }


}


public int A7


{


get { return a7; }


set { a7 = value; }


}


public int A8


{


get { return a8; }


set { a8 = value; }


}


public int A9


{


get { return a9; }


set { a9 = value; }


}


public int A10


{


get { return a10; }


set { a10 = value; }


}


public int A11


{


get { return a11; }


set { a11 = value; }


}


public int A12


{


get { return a12; }


set { a12 = value; }


}


}


}


项目源码在此


分别测试了10个,100个,1000个,10000个和1000,000(一百万)个实例创建和方法调用。其中一百万个简单类直接实例化/反射实例化和方法直接调用/反射调用的结果如下(我的电脑配置:奔腾双核E5700/4G DDR3 1333 内存/500G 7200转硬盘/ windows 7):


【注:我使用了win7的win32 API"QueryThreadCycleTime",XP上好像没有这个函数,需要使用GetThreadTimes,详细请参见:


而很多时候,我们开发中的一般系统,也不是十分大规模的使用反射,我曾设想在我的一个ERP系统中使用反射遍历实体类中的所有属性来生成SQL语句,而其中最多属性的一个类中,也只是80个属性,使用反射访问这个类的所有属性,也应该是毫秒级的事情,几十几百毫秒,对于CPU来说,当然慢得可怜,但对我们来说,是不可接受的吗?或许是,但是如果我们使用反射来编写出更好维护,更好的程序,那么对我来说,区区毫秒级的时间绝对可以接受。不过,凡是涉及到技术,特别在开发领域,都会有争论。读者如果有自己的见解,欢迎提出来研究研究。


最后,祝大家中秋节国庆快乐!!

相关文章
|
2月前
|
数据采集 存储 C#
C# 爬虫技术:京东视频内容抓取的实战案例分析
C# 爬虫技术:京东视频内容抓取的实战案例分析
|
6天前
|
SQL API 定位技术
基于C#使用winform技术的游戏平台的实现【C#课程设计】
本文介绍了基于C#使用WinForms技术开发的游戏平台项目,包括项目结构、运行截图、实现功能、部分代码说明、数据库设计和完整代码资源。项目涵盖了登录注册、个人信息修改、游戏商城列表查看、游戏管理、用户信息管理、数据分析等功能。代码示例包括ListView和ImageList的使用、图片上传、图表插件使用和SQL工具类封装,以及高德地图天气API的调用。
基于C#使用winform技术的游戏平台的实现【C#课程设计】
|
4月前
|
编译器 API C#
技术心得记录:深入分析C#键盘勾子(Hook)拦截器,屏蔽键盘活动的详解
技术心得记录:深入分析C#键盘勾子(Hook)拦截器,屏蔽键盘活动的详解
|
26天前
|
人工智能 开发框架 算法
C#/.NET/.NET Core技术前沿周刊 | 第 2 期(2024年8.19-8.25)
C#/.NET/.NET Core技术前沿周刊 | 第 2 期(2024年8.19-8.25)
|
26天前
|
传感器 应用服务中间件 Linux
C#/.NET/.NET Core技术前沿周刊 | 第 3 期(2024年8.26-8.31)
C#/.NET/.NET Core技术前沿周刊 | 第 3 期(2024年8.26-8.31)
|
26天前
|
人工智能 算法 C#
C#/.NET/.NET Core技术前沿周刊 | 第 1 期(2024年8.12-8.18)
C#/.NET/.NET Core技术前沿周刊 | 第 1 期(2024年8.12-8.18)
|
2月前
|
图形学 C# 开发者
全面掌握Unity游戏开发核心技术:C#脚本编程从入门到精通——详解生命周期方法、事件处理与面向对象设计,助你打造高效稳定的互动娱乐体验
【8月更文挑战第31天】Unity 是一款强大的游戏开发平台,支持多种编程语言,其中 C# 最为常用。本文介绍 C# 在 Unity 中的应用,涵盖脚本生命周期、常用函数、事件处理及面向对象编程等核心概念。通过具体示例,展示如何编写有效的 C# 脚本,包括 Start、Update 和 LateUpdate 等生命周期方法,以及碰撞检测和类继承等高级技巧,帮助开发者掌握 Unity 脚本编程基础,提升游戏开发效率。
40 0
|
2月前
|
前端开发 开发者 Apache
揭秘Apache Wicket项目结构:如何打造Web应用的钢铁长城,告别混乱代码!
【8月更文挑战第31天】Apache Wicket凭借其组件化设计深受Java Web开发者青睐。本文详细解析了Wicket项目结构,帮助你构建可维护的大型Web应用。通过示例展示了如何使用Maven管理依赖,并组织页面、组件及业务逻辑,确保代码清晰易懂。Wicket提供的页面继承、组件重用等功能进一步增强了项目的可维护性和扩展性。掌握这些技巧,能够显著提升开发效率,构建更稳定的Web应用。
76 0
|
2月前
|
前端开发 程序员 API
从后端到前端的无缝切换:一名C#程序员如何借助Blazor技术实现全栈开发的梦想——深入解析Blazor框架下的Web应用构建之旅,附带实战代码示例与项目配置技巧揭露
【8月更文挑战第31天】本文通过详细步骤和代码示例,介绍了如何利用 Blazor 构建全栈 Web 应用。从创建新的 Blazor WebAssembly 项目开始,逐步演示了前后端分离的服务架构设计,包括 REST API 的设置及 Blazor 组件的数据展示。通过整合前后端逻辑,C# 开发者能够在统一环境中实现高效且一致的全栈开发。Blazor 的引入不仅简化了 Web 应用开发流程,还为习惯于后端开发的程序员提供了进入前端世界的桥梁。
48 0
|
4月前
|
关系型数据库 C# 数据库
技术笔记:MSCL超级工具类(C#),开发人员必备,开发利器
技术笔记:MSCL超级工具类(C#),开发人员必备,开发利器
45 3