2014-09-04 63 views
0

下面是一个简单的问题。Reflection.Emit Performance

比方说,我们要展开一个循环的方法,如:

public int DoSum1(int n) 
{ 
    int result = 0; 
    for(int i = 1;i <= n; i++) 
    { 
     result += i; 
    } 
    return result; 
} 

到一个方法只能进行简单的加法:

public int DoSum2() 
{ 
    return 1+2+3+4+5+6+7+8+9+10+11+12+13+14+15+16+17+18+19+20; 
} 

[http://etutorials.org/Programming/Programming+C.Sharp/Part+III+The+CLR+and+the+.NET+Framework/Chapter+18.+Attributes+and+Reflection/18.3+Reflection+Emit/][1]

从逻辑上讲,我们要在某些时候需要代码在IL中创建DoSum2。 在这个IL代码中,我们将使用与未优化方法相同的迭代计数来执行实际循环。

什么,如果所需的代码生成将使用类似的时间来执行量创造一个超快速的动态方法的点???

或许你可以举个例子,当它在一点,实在值得类似情况下使用的Emit?

回答

1

什么,如果所需的代码生成将使用的时间类似的量来执行

这是不是真的具体到Reflection.Emit创建一个超快速的动态方法的地步,但运行时一般的代码生成,所以我会相应地回答。

首先,我不建议使用的代码生成简单地进行微优化,编译器通常执行像展开循环。让JIT编译器做它的工作。

其次,你是对的,因为通常只会生成只会执行一次的代码。发射和JIT编译IL所需的时间并不是非实质性的。如果它将被执行很多次,你只应该烦恼生成代码。

现在,肯定案件下,运行时代码生成可以证明是有益的。事实上,这是一项我非常重视的技术。我在需要处理大量动态数据的电子交易环境中工作。这引起了一些问题,最重要的是内存使用和吞吐量。

我们的交易应用程序需要保持大量的内存中的数据,使每个记录的足迹是至关重要的。诸如地图/字典之类的动态数据结构比具有优化的字段布局的“POCO”类效率低,并且根据设计,可能需要装箱一些值。一旦数据的形状已知,我就通过生成客户端存储类来避免这种开销。实际上,内存布局就像我在编译时知道数据的形状一样。

吞吐量也是一个主要问题; (de)序列化动态数据通常会涉及一些额外的内省和额外的间接层。需要序列化记录?好的,首先你需要查询字段是什么。然后,对于每个字段,您需要确定其类型,然后选择该类型的序列化程序,然后调用序列化程序。如果您的数据结构具有可选字段,则可能需要执行一些额外的预处理,例如计算出席地图的大小以及出席地图中的哪些位对应于哪些字段。如果您需要处理数据,则所有这些开销都会成为一个真正的问题。我通过在服务器端和客户端上生成专门的(de)序列化器来避免这种开销。由于串行器是按需生成的,因此它们可以知道数据的确切形状,并且可以像手动优化的串行器那样高效地读取/写入数据。当你在很高的频率下进行大量数据更新时,这可能会产生巨大的差异。

现在,请记住,我们是一个边缘案例。大多数应用程序没有我们所具有的积极的内存和吞吐量要求,因此运行时代码生成不是必需的。如果您真的需要,并且您已经耗尽所有其他可能性,则应该只使用该路线。虽然它可以帮助提高性能,但生成的代码可能很难调试和维护。

+0

“这不是真的专用于Reflection.Emit”: 正确的,我可以使用CodeDom,而不是慢。 我想整个想法是,这样的性能优化是为了再次使用相同的生成装配。 ...在我的例子中,该方法不可重复使用。 “ ”“POCO”类与优化的字段布局“: 我想知道如果你正在谈论在MSIL中使用较短的地址?否则请解释。 您提到的关于序列化的例子非常有趣。事实上,你将不得不运行大量的条件反射代码。 – Olograph 2014-09-04 21:23:02

+0

关于我的POCO评论,我的意思是,如果底层数据存储在适当类型的字段中,而不是稀疏映射/字典,则封装更紧凑。运行时可以安排这些字段以消除不必要的填充,并且最终不会像使用散列表中的未使用的插槽(更不用说不必要的填充值)。 – 2014-09-05 13:33:55

+0

好点,谢谢! – Olograph 2014-09-06 21:31:59