反射这玩意,一直以来都是慢的代名词。一说XXX系统大量的反射,好多人第一印象就是会慢。
但是呢,我们又不得不使用反射来做一些事情,毕竟这玩意可以说啥都能干了对吧。
当然.Net也提供了一些性能更高的方法。
比如SG
,这玩意是性能最好的方案,它在编译的时候生成代码,运行的时候一点反射没有,同时也完美支持Native AOT。但是呢,它还不是真正的动态生成,只能说是开发时动态。所以更适合一些框架程序使用来提高执行效率。
还有比如Emit
,这玩意是动态编织IL代码的,效率也比反射要快。但是呢,写起来极度复杂,10个人有8个都挠头。
所以,.Net 7里反射还是非常重要的一部分,也针对它做了一些比较牛逼的优化。
我们知道,给
MethodBase
使用CreateDelegate<T>
来创建一个委托,然后调用这个委托是最佳方法。但是呢,我们编译的时候经常是不知道这个方法签名的,也就是没法生成这个委托。部分库已经使用Emit
来生成代码提高速度了。但是我们普通用户显然区写一堆Emit
是不现实的。.Net 7优化后,会把我们的反射代码优化为DynamicMethod
形式的委托,然后调用。我们来看一下数据
private MethodInfo _method;
[GlobalSetup]
public void Setup() => _method = typeof(Program).GetMethod("MyMethod", BindingFlags.NonPublic | BindingFlags.Static);
[Benchmark]
public void MethodInfoInvoke() => _method.Invoke(null, null);
private static void MyMethod() { }
我们可以看到,这玩意速度提升了好几倍。
反射还有一个用处就是对类型、方法、属性等等这些东西进行获取。一些其他的改进也会影响到这一部分。比如.Net最近一直在做的把原生类型转换为托管类型的工作,就产生了这么一个东西。
[Benchmark]
public Type GetUnderlyingType() => Enum.GetUnderlyingType(typeof(DayOfWeek));
是的,原生类型转换为托管类型,不但没有拖慢反射,反而让它快了好几倍。
同样的例子,有大量的AssemblyName
的内容从原生转向了CoreLib,所以Activator.CreateInstance
也跟着变快了。
private readonly string _assemblyName = typeof(MyClass).Assembly.FullName;
private readonly string _typeName = typeof(MyClass).FullName;
public class MyClass { }
[Benchmark]
public object CreateInstance() => Activator.CreateInstance(_assemblyName, _typeName);
这玩意虽然没有那么夸张,但是提升可以说也是不小了。
RuntimeType.CreateInstanceImpl
现在使用Type.EmptyTypes
代替了new Type[0]
,所以节省了一部分开销。
[Benchmark]
public void CreateInstance() => Activator.CreateInstance(typeof(MyClass), BindingFlags.NonPublic | BindingFlags.Instance, null, Array.Empty<object>(), null);
internal class MyClass
{
internal MyClass() { }
}
我们再回到AssemblyName
来,AssemblyName
里把AssemblyName.FullName
的实现由StringBuilder
改为了ArrayPool<char>
,所以:
private AssemblyName[] _names = AppDomain.CurrentDomain.GetAssemblies().Select(a => new AssemblyName(a.FullName)).ToArray();
[Benchmark]
public int Names()
{
int sum = 0;
foreach (AssemblyName name in _names)
{
sum += name.FullName.Length;
}
return sum;
}
另外由于JIT编译器又进化了,现在可以在编译过程中计算结果,所以:
[Benchmark]
public bool IsByRefLike() => typeof(ReadOnlySpan<char>).IsByRefLike;
是的,你没看错,时间是0,因为这里在运行的时候已经不需要计算了,直接就是个赋值操作,所以这个时间就。。。
我们来看一下生成的汇编
; Program.IsByRefLike()
mov eax,1
ret
; Total bytes of code 6
这就是反射优化的主要内容。反正就高喊666,知道反射又快了,用起来心里负担又小了就搞定了^ ^。