DynamicMethod性能下降

时间:2012-09-25 19:49:31

标签: c# .net cil reflection.emit

我试图通过专门为该任务生成IL来提高项目中某些代码的性能。

此任务目前通过对数组元素执行for循环并通过接口运行各种方法来完成。我想用没有任何虚拟/接口调用(通过直接执行所需操作)专门执行此任务的IL替换它。

出于某种原因,此DynamicMethod的运行时性能 比执行每个元素的接口调用的原始代码的运行时性能慢。我能看到的唯一原因是我的DynamicMethod非常大(每个元素元素都有一些指令)。

我认为这可能是第一次因为JIT而缓慢的调用,但事实并非如此。所有来电都比较慢。有没有人遇到过这样的事情?

修改

这里的人请求代码..原始代码非常大,但这是一个缩小版本(它是一个自动微分代码,用于使用反向模式AD计算函数梯度)。我的数组中的所有元素都继承了以下类

abstract class Element
{
    public double Value
    public double Adjoint
    public abstract void Accept(IVisitor visitor)
}

我有两个派生自元素的类。为简单起见,我将仅定义以下两个

class Sum : Element
{
    public int IndexOfLeft;   // the index in the array of the first operand
    public int IndexOfRight;  // the index in the array of the second operand
    public abstract void Accept(IVisitor visitor) { visitor.Visit(this); }
}

class Product : Element
{
    public int IndexOfLeft;   // the index in the array of the first operand 
    public int IndexOfRight;  // the index in the array of second first operand 
    public abstract void Accept(IVisitor visitor) { visitor.Visit(this); }
}

以下是访问者的实现:

class Visitor : IVisitor
{
    private Element[] array;

    public Visitor(Element[] array) { this.array = array; }

    public void Visit(Product product)
    {
        var left = array[product.IndexOfLeft].Value;
        var right = array[product.IndexOfRight].Value;

        // here we update product.Value and product.Adjoint according to some mathematical formulas involving left & right
    } 

    public void Visit(Sum sum)
    {
        var left = array[sum.IndexOfLeft].Value;
        var right = array[sum.IndexOfRight].Value;

        // here we update sum.Value and product.Adjoint according to some mathematical formulas involving left & right
    }       
}

我的原始代码如下:

void Compute(Element[] array)
{
    var visitor = new Visitor(array);
    for(int i = 0; i < array.Length; ++i)
        array[i].Accept(visitor);
}

我的新代码试图做这样的事情

void GenerateIL(Element[] array, ILGenerator ilGenerator)
{
    for(int i = 0; i < array.Length; ++i)
    {
        // for each element we emit calls that push "array[i]" and "array" 
        // to the stack, treating "i" as constant,
        // and emit a call to a method similar to Visit in the above visitor that 
        // performs a computation similar to Visitor.Visit.
    }
}

然后我调用生成的代码..并且它在调用Compute(array)时执行比访问者模式的 double dispatch 更慢;

4 个答案:

答案 0 :(得分:1)

如果我理解正确的话,你试图通过发出代码本身并直接调用方法来消除调用虚方法的开销。例如,不要调用数千个虚拟函数来调用一个虚函数。

但是,您希望不同的对象具有相同的界面。您只能通过虚拟呼叫来实现此目的。实现接口,使用委托或发出代码。是的,即使您发出代码,也需要某种接口来调用该方法,这可能是调用Delegate或将其转换为func / action预定义委托。

如果你想有一些有效的方法发出代码我建议使用“LambdaExpression.CompileToMethod”。该方法是一个方法构建器,你已经有一个我假设的方法。你可以在互联网上看到很多例子。但是,这仍然会导致虚拟通话。

因此,如果您希望在许多对象中具有相同的界面,则除非将对象放入与其类型相关的不同容器中,否则无法进行非虚拟调用。这是反对多态的。

答案 1 :(得分:1)

你试过通过将循环封闭在try-catch块中来欺骗JIT使用更快的内存吗?这也有一个优点,即删除退出条件,因此可以节省一些IL。

try
{
    for (int i= 0; ; i++)
    {
        var visitor = new Visitor(array);
        for(int i = 0;; ++i)
            array[i].Accept(visitor);
    }
}
catch (IndexOutOfRangeException)
{ }

它看起来很糟糕,但它利用了JIT内存分配的怪癖,可能有助于解决你的IL性能问题。

有关详细信息,请参阅Optimisation of for loop

答案 2 :(得分:1)

如果您真的对超级优化代码感兴趣,那么您需要学习IL!

在以下链接中查看IL OP代码......

http://msdn.microsoft.com/en-us/library/system.reflection.emit.opcodes(v=vs.95).aspx

也可以使用ILDasm来查看从方法中生成的代码......

虽然我怀疑你不能非常优化IL,并且用C ++编写并调用非托管代码会好得多......

只是想到你......

祝你好运 马修

答案 3 :(得分:1)

我很好奇为什么标题是动态方法。当您生成IL时。 您的意思是动态IL生成然后静态执行。要么 你是否也生成使用IL等效的c#动态关键字的IL?

动态(运行时)IL

我假设代码只被jitted一次。你已经检查了这个。

在提供的示例中使用数组而不是泛型增加了神秘感。 问题在于生成的IL而不是生成IL的代码。 但是如果您在生成的IL中使用了ARRAY,那么您将使用box unbox。 昂贵的堆叠/堆叠和背面操作。

你是否IL生成使用BOX和UNBOX的代码。 IL运营?我会从那里开始。

收集初始化是下一个要查看的地方。

其他一些快速的想法: 为了节省方法调用开销而标记大量代码段可能会对JIT时间产生负面影响。由于编译器必须处理整个方法/成员。 如果你有小方法,它会在需要时编译。但你说它不是JIT问题。

这些LARGE方法可能有大量的堆栈操作吗?

经常被调用的方法中的任何大型Value对象? 例如,具有&gt;的STRUCT对象64字节?每次都要分配和销毁堆栈。

RedGate性能分析器告诉您什么? http://www.red-gate.com/products/dotnet-development/ants-performance-profiler/?utm_source=google&utm_medium=cpc&utm_content=unmet_need&utm_campaign=antsperformanceprofiler&gclid=CIXamdiA6bICFQYcpQodDA0Akw

BTW我是IL的新手。只是抛出一些想法。

祝你好运。