0

这是一个简单的问题。

假设我们要展开一个循环方法,例如:

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]

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

如果生成它所需的代码将使用相似的时间来执行,那么创建一个超快速的动态方法有什么意义???

也许你可以举个例子,在类似的情况下什么时候值得使用 Emit?

4

1 回答 1

1

如果生成它所需的代码将使用相似的时间来执行,那么创建一个超快速的动态方法有什么意义?

这并不是真正特定于Reflection.Emit,而是一般的运行时代码生成,所以我会相应地回答。

首先,我不建议仅使用代码生成来执行编译器通常执行的微优化,例如循环展开。让 JIT 编译器完成它的工作。

其次,您是对的,生成只会执行一次的代码通常没有什么意义。发出和 JIT 编译 IL 所需的时间并非微不足道。如果代码会被执行多次,您应该只费心生成代码。

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

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

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

现在,请记住,我们是一个边缘案例。大多数应用程序没有我们的严格的内存和吞吐量要求,因此不需要生成运行时代码。如果你真的需要它,你应该只走那条路,并且你已经用尽了所有其他可能性。尽管它可以帮助提高性能,但生成的代码可能很难调试和维护。

于 2014-09-04T15:15:54.687 回答