一文带你了解 C# DLR 的世界(DLR 探秘)

2020-02-22 21:57:46刘景俊

上文的两个变量staticCallSite1,staticCallSite2 是静态变量,起到缓存的作用。

这里涉及到了DLR核心三个概念

    ExpressTree(表达式树):通过CLR运行时用抽象语法树(AST)生成代码并执行。并且它也是用来与动态语言交互的主要工具(如Python,JavaScript 等) CallSite(调用点):当我们写的调用动态类型的方法,这就是一个调用点。这些调用都是静态函数,是能够缓存下来的,所以在后续的调用,如果发现是相同类型的调用,就会更快的运行。 Binder(绑定器):除了调用点之外,系统还需要知道这些方法如何调用,就比如例子中的通过调用Binder.InvokeMember方法,以及是那些对象类型调用的方法等信息。绑定器也是可以缓存的

总结

DLR运行过程我们总结起来就是,在运行时DLR利用编译运行期间生成的表达式树、调用点、绑定器代码,以及缓存机制,我们就可以做到计算的重用来达到高性能。ASP.NET页面缓存常见的4种方式

现在我们就知道了为什么DLR能干出与反射相同的效果,但是性能要远比反射要高的原因了。

补充说明

刚看到评论里的同学提到了reflection与dynamic的性能测试比较,发现反射性能占据明显的优势。事实上,从那个例子来看,恰恰说明了DLR的问题。这里我先列出他的测试代码

const int Num = 1000 * 100;
{
 var mi = typeof(XXX).GetMethod("Go");
 var go1 = new XXX();
 for (int i = 0; i < Num; i++)
 {
 mi.Invoke(go1, null);
 }
}
{
 dynamic go1 = new XXX();
 for (int i = 0; i < Num; i++)
 {
 go1.Go();
 }
}

在这个测试中,已经将反射出来的元数据信息缓存到局部变量 mi,所以在调用方法的时候,实际上用到的是已经缓存下来的 mi。那么在没有缓存优势的情况,说明DLR性能是不如 MethodInfo+Invoke 的。

其实在文章总结的时候也强调了,利用缓存机制达到多次重复计算的重用来提高性能

那么我们在看一个例子:

public void DynamicMethod(Foo f) {
 dynamic d = f;
 d.DoSomething();
}

public void ReflectionMethod(Foo f) {
 var m = typeof(Foo).GetMethod("DoSomething");
 m?.Invoke(f, null);
}

方法 DoSomething 只是一个空方法。现在我们来看执行结果

// 执行时间
var f = new Foo();
Stopwatch sw = new Stopwatch();
int n = 10000000;
sw.Start();
for (int i = 0; i < n; i++) {
 ReflectionMethod(f);
}
sw.Stop();
Console.WriteLine("ReflectionMethod: " + sw.ElapsedMilliseconds + " ms");

sw.Restart();
for (int i = 0; i < n; i++) {
 DynamicMethod(f);
}
sw.Stop();
Console.WriteLine("DynamicMethod: " + sw.ElapsedMilliseconds + " ms");

// 输出
ReflectionMethod: 1923 ms
DynamicMethod: 223 ms

这里我们就能明显看出执行时间的差距了。实际上DLR的执行过程我用下面伪代码表示