反射这玩意,一直以来都是慢的代名词。一说XXX系统大量的反射,好多人第一印象就是会慢。


但是呢,我们又不得不使用反射来做一些事情,毕竟这玩意可以说啥都能干了对吧。

当然.Net也提供了一些性能更高的方法。

比如SG,这玩意是性能最好的方案,它在编译的时候生成代码,运行的时候一点反射没有,同时也完美支持Native AOT。但是呢,它还不是真正的动态生成,只能说是开发时动态。所以更适合一些框架程序使用来提高执行效率。

还有比如Emit,这玩意是动态编织IL代码的,效率也比反射要快。但是呢,写起来极度复杂,10个人有8个都挠头。


所以,.Net 7里反射还是非常重要的一部分,也针对它做了一些比较牛逼的优化。

  1. 我们知道,给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,知道反射又快了,用起来心里负担又小了就搞定了^ ^。

09-27 09:51