我试图通过专门为该任务生成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 更慢;
答案 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
我假设代码只被jitted一次。你已经检查了这个。
在提供的示例中使用数组而不是泛型增加了神秘感。 问题在于生成的IL而不是生成IL的代码。 但是如果您在生成的IL中使用了ARRAY,那么您将使用box unbox。 昂贵的堆叠/堆叠和背面操作。
你是否IL生成使用BOX和UNBOX的代码。 IL运营?我会从那里开始。
收集初始化是下一个要查看的地方。
其他一些快速的想法: 为了节省方法调用开销而标记大量代码段可能会对JIT时间产生负面影响。由于编译器必须处理整个方法/成员。 如果你有小方法,它会在需要时编译。但你说它不是JIT问题。
这些LARGE方法可能有大量的堆栈操作吗?
经常被调用的方法中的任何大型Value对象? 例如,具有&gt;的STRUCT对象64字节?每次都要分配和销毁堆栈。
BTW我是IL的新手。只是抛出一些想法。
祝你好运。