C#中的循环反转加速应用程序

C#中的循环反转加速应用程序,c#,performance,optimization,loops,C#,Performance,Optimization,Loops,我们正在使用EmguCV开发一个视频处理应用程序,最近不得不做一些像素级的操作。我最初编写了循环以遍历图像中的所有像素,如下所示: for (int j = 0; j < Img.Width; j++ ) { for (int i = 0; i < Img.Height; i++) { // Pixel operation code } } 我非常惊讶地发现,代码执行的时间只有的一半 我能想到的唯一一件事是每次访问属性时循环中发生的比较,它不

我们正在使用EmguCV开发一个视频处理应用程序,最近不得不做一些像素级的操作。我最初编写了循环以遍历图像中的所有像素,如下所示:

for (int j = 0; j < Img.Width; j++ )
{
    for (int i = 0; i < Img.Height; i++)
    {
        // Pixel operation code
    }
}
我非常惊讶地发现,代码执行的时间只有的一半


我能想到的唯一一件事是每次访问属性时循环中发生的比较,它不再需要这样做。这就是加速的原因吗?还是有别的事?看到这一进步我很激动。如果有人能澄清原因,我会很高兴的。

这是因为CPU就像曲棍球运动员一样,后退时速度更快;-)

更严肃地说:
这与循环的方向没有任何关系,而是与以下事实有关:在原始构造中,循环控制条件意味着取消对Img对象的引用,以索引到其宽度或高度属性(对于循环中的每个迭代和单个迭代),第二个构造仅对这些属性求值一次。
此外,新条件针对值0进行测试的事实,甚至可以节省立即值的加载。 这可能解释了差异(假设内部所做的工作相对最小,即+/-与测试Object.Property的工作相同,因为您表示大约有50%的增益)

编辑:

参见Michael Stum的回答,这表明Img.Width/Height参考比想象的更昂贵。与属性有时发生的情况一样,对象的实现可能会运行大量代码来生成值(例如,每次它可能会进行一系列数学运算以获得宽度,而不是以某种方式缓存它等等)。这个Img对象似乎就是这种情况,因此只需执行一次(如果您确信该值在循环逻辑期间保持不变).

加快速度的不是循环反转,而是访问宽度和高度属性的次数要少得多。

我假设您使用的是System.Drawing.Image类?查看.Width和.Height的实现,我看到它们对GDI+(GdipGetImageHeight和GdipGetImageWidth在gdiplus.dll中)进行了函数调用,这似乎相当昂贵


向后调用一次,而不是在每次迭代中调用。

区别不是分支的成本,而是在内部循环中获取对象属性
Img.Width
Img.Height
。优化器无法知道这些是循环中的常量

通过这样做,您应该可以获得相同的性能加速

const int Width = Img.Width;
const int Height = Img.Height;
for (int j = 0; j < Width; j++ )
{
    for (int i = 0; i < Height; i++)
    {
        // Pixel operation code
    }
}
const int Width=Img.Width;
常数内高度=内高度;
对于(int j=0;j
编辑: 正如Joshua所建议的,在内部循环中增加宽度将使您按顺序遍历内存,这将提高缓存的一致性,并且可能更快。(取决于位图的大小)

const int Width=Img.Width;
常数内高度=内高度;
对于(int i=0;i
虽然我不确定原因是什么,但您可以通过第一种方式运行它来测试您的理论,但首先将属性缓存在局部变量中,这样您就不必每次迭代都访问该属性。而当我们进行此操作时——如果您使用
GetPixel
SetPixel
进行像素操作,你有一个更大的性能问题。请参阅本文:它将循环管理中的操作数量减少了一半;比较和增量已成为比较。如果“像素操作”是一个nop(就像在代码中一样),那么仅此一项就可以使执行时间减半。@BenM Bobpowell早已消失,但这一点非常好!我们经常假设引用这些属性是一种廉价的操作,忘记了底层对象在生成值时可能会“花很长时间”……属性通常被认为是廉价的,也许是,但在循环中它会快速累积。此外,几乎在宽度之外的高度上循环。你的一级缓存会感谢你的。@Joshua:这是一个很好的建议,内循环的宽度将具有更好的引用位置。如果你在整个2d数组上迭代,如果你需要速度,了解它们在内存中的布局是至关重要的。与大多数事物一样,IIRC.Net是行的主要顺序。
const int Width = Img.Width;
const int Height = Img.Height;
for (int j = 0; j < Width; j++ )
{
    for (int i = 0; i < Height; i++)
    {
        // Pixel operation code
    }
}
const int Width = Img.Width;
const int Height = Img.Height;
for (int i = 0; i < Height; i++)
{
    for (int j = 0; j < Width; j++ )
    {
        // Pixel operation code
    }
}