C#(含Unity)unsafe指针快速反射第五篇(性能比较篇 )
C#(含Unity)unsafe指针快速反射第一篇(字段篇 )
C#(含Unity)unsafe指针快速反射第二篇(属性篇 )
C#(含Unity)unsafe指针快速反射第三篇(数组篇 )
C#(含Unity)unsafe指针快速反射第四篇(整合篇 )
Unity3D(已完成iL2cpp和Mono的编译)请访问:smopu/unity3d_quick_reflection: 在unity3D中使用指针进行快速反射 (github.com)
.Net Framework 和.Net Core请访问:smopu/CSharp_QuickReflection: C# .Net Framework Quick Reflection 快速反射 (github.com)
本次测试的Unity3D源代码在 smopu/UnityReflectionCompare: Unity下各种反射方式的性能比较 (github.com)
本次测试的.Net 源代码在 smopu/ReflectionCompare: 各种C#反射方式的性能比较 (github.com)
感谢冰封百度提供的帮助,我这里修改了他在博客上的Emit技术、表达式树技术的使用代码,博客原文Unity C# 反射性能优化 - 冰封百度的学习笔记 - SegmentFault 思否
感谢莫明棋妙对Unity平台的测试帮助,他的Git地址:https://github.com/ajycode
现在开始正式排位,啊不是,开始正式比较性能,在比赛之前,我跟各位观众约定一下本次测试的规则:
本次赛场分为.Net赛场和Unity赛场。我们把所有参赛选手都通过release条件下编译成Dll,然后测试工程使用一个MyClass类,测试工程在执行的时候分debug环境和release环境分别测试,主要以debug环境的结果为主,因为实际项目开发中,大部分项目都使用debug环境,只有部分功能库和插件会用release环境,而反射库是一个基本功能库,所以我们所有参赛选手使用release环境。
本次参赛选手有三位:指针反射PtrReflection,动态编译Emit,表达式树Expression。同时我们有三个对照组选手,1:原生反射System.Reflection,2:直接C#赋值和取值(后面展示源代码),3:通过switch判断字段/属性名字符串来赋值和取值(后面展示源代码)。
三个对照组选手的源代码不参与release条件编译。
本次比赛的MyClass类源代码公示:
public class MyClass
{
public int one;
public string str;
public Vector3 point;
public int One { get; set; }
public string Str { get; set; }
public Vector3 Point { get; set; }
public int[,,] oness;
public string[,] strss;
public Vector3[,] pointss;
public int[] ones;
public string[] strs;
public Vector3[] points;
public static int staticOne = 11;
public static string staticString = "ADc%";
public static Vector3 staticVector3 = new Vector3(1.1f, 2.2f, 3.3f);
}
参赛选手将要对 one、str、point三个字段以及One、Str、Point三个属性进行赋值和取值,字段和属性分开比较,同时还加入不确定类型(object类型)和确定类型的双重比较,循环1000000比较耗时,耗时少的参赛选手将胜出,但是考虑到程序每次执行耗时都会略有不同,所以耗时相差在百分之十之内视为平局,耗时相差的比较方法为:耗时较高的时间/耗时较低的时间-100%
不确定类型(object类型)比较方式就是把字段和属性只看做object类型赋值和取值,确定类型比较方式是在赋值和取值时能确定字段和属性的类型。
本次比赛所有参赛选手在赋值和取值时,有权知道反射的内容是字段还是属性,就像原生反射的那样:FieldInfo和PropertyInfo所区分的那样。
本次比赛以debug环境为主,release环境为参考环境,只有当debug环境下双方选手为平局时,才考虑release环境。debug环境执行三次展示耗时,release执行一次展示耗时。
三名参赛选手指针反射PtrReflection,动态编译Emit,表达式树Expression都有各种的Wrapper对象,我们把Wrapper对象的实例化不计入耗时比较中。每1个需要反射的类对应一个Wrapper对象,在实际工程中第一次反射某个类时需要先生成再通过Wrapper对象,后面再反射这个类的时候只需要1次查找把对应Type的Wrapper对象找到,因为Wrapper对象每个类都只需要生成一次,所以本次比赛不考虑它生成的时间,而且本次比赛三位选手都把Wrapper对象提前生成,所以不影响比赛结果。我们的三个对照组选手均没有Wrapper对象。
三位参赛选手的Wrapper对象如下:
TypeAddrReflectionWrapper reflectionWrapper = new TypeAddrReflectionWrapper(typeof(MyClass));
EmitWrapperType emitWrapperType = new EmitWrapperType(typeof(MyClass));
ExpressionWrapperType expressionWrapperType = new ExpressionWrapperType(typeof(MyClass));
现在展示“直接C#赋值和取值”的源代码
obj.one = 13;
v1 = obj.one;
obj.str = "sad2";
v2 = obj.str;
obj.point = new Vector3(1.1f, 2.2f, 3.3f);
v3 = obj.point;
下面是"switch"选手的源代码:
obj.one = 13;
v1 = obj.one;
obj.str = "sad2";
v2 = obj.str;
obj.point = new Vector3(1.1f, 2.2f, 3.3f);
v3 = obj.point; static void SwitchSetData(MyClass myClass, object data, string name)
{
switch (name)
{
case "one":
myClass.one = (int)data;
break;
case "str":
myClass.str = (string)data;
break;
case "point":
myClass.point = (Vector3)data;
break;
case "One":
myClass.One = (int)data;
break;
case "Str":
myClass.Str = (string)data;
break;
case "Point":
myClass.Point = (Vector3)data;
break;
case "oness":
myClass.oness = (int[,,])data;
break;
case "strss":
myClass.strss = (string[,])data;
break;
case "pointss":
myClass.pointss = (Vector3[,])data;
break;
case "ones":
myClass.ones = (int[])data;
break;
case "strs":
myClass.strs = (string[])data;
break;
case "points":
myClass.points = (Vector3[])data;
break;
}
}
static object SwitchGetData(MyClass myClass, string name)
{
switch (name)
{
case "one":
return myClass.one;
case "str":
return myClass.str;
case "point":
return myClass.point;
case "One":
return myClass.One;
case "Str":
return myClass.Str;
case "Point":
return myClass.Point;
case "oness":
return myClass.oness;
case "strss":
return myClass.strss;
case "pointss":
return myClass.pointss;
case "ones":
return myClass.ones;
case "strs":
return myClass.strs;
case "points":
return myClass.points;
default:
return null;
}
}
我们可以看到,这就是直接通过字符串来赋值取值的硬编码。
对规则有疑问的观众请前往评论区发表您的观点,您的参与将有机会获得额外大礼!!!
下面进行.Net赛区的debug环境比较,以下是三次耗时:
最后一次的成绩,我把文本给出来
C#, 字段: 13.1569 毫秒
switch, 字段: 470.486 毫秒
PtrReflection, 字段, object类型: 186.0537 毫秒
Emit, 字段, object类型: 248.2762 毫秒
Expression, 字段, object类型: 227.9534 毫秒
System.Reflection, 字段, object类型: 1092.092 毫秒
PtrReflection, 属性, object类型: 162.2727 毫秒
Emit, 属性, object类型: 206.9175 毫秒
Expression, 属性, object类型: 207.5198 毫秒
System.Reflection, 属性, object类型: 1966.1938 毫秒
PtrReflection, 字段,指定类型(值类型无装箱,传参无拷贝):92.1995 毫秒
Emit, 字段, 指定类型(值类型无装箱): 173.3449 毫秒
Expression, 字段, 指定类型(值类型无装箱): 149.5582 毫秒
PtrReflection, 属性,指定类型(值类型无装箱): 122.3596 毫秒
Emit, 属性, 指定类型(值类型无装箱): 183.5979 毫秒
Expression, 属性, 指定类型(值类型无装箱): 207.9753 毫秒
下面进行.Net赛区的release环境比较,以下是1次耗时:
C#, 字段: 2.9452 毫秒
switch, 字段: 97.3043 毫秒
PtrReflection, 字段, object类型: 108.2992 毫秒
Emit, 字段, object类型: 223.9893 毫秒
Expression, 字段, object类型: 290.535 毫秒
System.Reflection, 字段, object类型: 1219.6261 毫秒
PtrReflection, 属性, object类型: 110.1392 毫秒
Emit, 属性, object类型: 137.3991 毫秒
Expression, 属性, object类型: 166.3366 毫秒
System.Reflection, 属性, object类型: 1862.1221 毫秒
PtrReflection, 字段,指定类型(值类型无装箱,传参无拷贝):31.7233 毫秒
Emit, 字段, 指定类型(值类型无装箱): 129.0019 毫秒
Expression, 字段, 指定类型(值类型无装箱): 119.0279 毫秒
PtrReflection, 属性,指定类型(值类型无装箱): 43.4737 毫秒
Emit, 属性, 指定类型(值类型无装箱): 122.3448 毫秒
Expression, 属性, 指定类型(值类型无装箱): 158.7886 毫秒
有观众可能疑惑,为什么switch硬编码耗时在debug环境里面反而这么慢?因为switch在debug环境中效率就是很低,跟字符串字典查询差不多,再加上转换为object的装箱操作,所以就比三位参赛选手都慢,而switch硬编码在release里面才是真正提高了效率,然而在relesase环境下仍然比不过指针反射PtrReflection,原因是因为指针反射用了更高效的字符串匹配算法。(在本人后面的文章会讲解更高效的字符串匹配算法)
其他两个参照组还是不出意料,C#直接读写的速度肯定是最快的,而原生的反射是最慢的。
三位选手在object类型比较中其实成绩差不多,Emit综合而言效率最低。在指定类型比较中指针反射效率拉开了其他两位选手一段距离,原因是指针反射传参时(值类型传参会拷贝参数)无拷贝,直接指针操作地址赋值。
在release模式,指针反射完美胜出。但我们不考虑release模式。
Unity赛场理论上我们还会考虑AOT环境下的问题,也就是IL2Cpp、iOS、WebGL等平台。但是Expression和Emit都用到及时编译(JIT)技术,导致AOT环境下不能使用。
现在开始Unity赛场的比赛,Unity赛场,采用编辑器模式和mono编译模式,由于Expression和Emit在IL2Cpp中不能使用,所以不采取IL2Cpp编译模式。
第一次比较:
第二次比较
第三次比较
第三次比较结果的文本:
C#, 字段: 1.6263 毫秒
switch, 字段: 113.5346 毫秒
PtrReflection, 字段, object类型: 158.5009 毫秒
Emit, 字段, object类型: 162.632 毫秒
Expression, 字段, object类型: 146.8331 毫秒
System.Reflection, 字段, object类型: 1678.3367 毫秒
PtrReflection, 属性, object类型: 174.0234 毫秒
Emit, 属性, object类型: 166.9311 毫秒
Expression, 属性, object类型: 174.8529 毫秒
System.Reflection, 属性, object类型: 2459.9966 毫秒
PtrReflection, 字段,指定类型(值类型无装箱,传参无拷贝):32.1415 毫秒
Emit, 字段, 指定类型(值类型无装箱): 77.8912 毫秒
Expression, 字段, 指定类型(值类型无装箱): 79.1044 毫秒
PtrReflection, 属性,指定类型(值类型无装箱): 33.6443 毫秒
Emit, 属性, 指定类型(值类型无装箱): 86.6389 毫秒
Expression, 属性, 指定类型(值类型无装箱): 86.7509 毫秒
下面是mono编译成exe执行后的比较结果:
可以看到,在Unity赛场,指针反射在Object类型读写属性的效率上还是比较落后,但综合实力仍然领先。
目前,本人宣布C#第一届反射效率比赛.Net赛场比赛结果:指针反射PtrReflection荣获冠军,表达式树Expression荣获亚军,动态编译Emit荣获季军!让我们恭喜各位选手!
想要报名参赛的选手请前往评论区留言,您的报名将会获得惊喜!!!
本站大部分文章、数据、图片均来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了您的权益请来信告知我们删除。邮箱:1451803763@qq.com