我们正在使用EmguCV开发视频处理应用程序,最近不得不做一些像素级操作。我最初编写循环来遍历图像中的所有像素,如下所示:
for (int j = 0; j < Img.Width; j++ )
{
for (int i = 0; i < Img.Height; i++)
{
// Pixel operation code
}
}
执行循环的时间非常糟糕。然后我在EmguCV论坛上发布了一个建议来切换这样的循环:
for (int j = Img.Width; j-- > 0; )
{
for (int i = Img.Height; i-- > 0; )
{
// Pixel operation code
}
}
我很惊讶地发现代码执行的时间是一半!
我唯一能想到的是每次访问一个属性时循环中发生的比较,它不再需要。这是加速的原因吗?或者还有其他什么?我很高兴看到这种改善。如果有人能澄清其原因,我会很高兴。
答案 0 :(得分:14)
差异不在于分支的成本,而是在内部循环中提取对象属性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
}
}
正如Joshua建议的那样,将Width放在内部循环中会让你顺序遍历内存,这将是更好的缓存一致性,并且可能更快。 (取决于你的位图有多大)。
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
}
}
答案 1 :(得分:13)
我假设您使用的是System.Drawing.Image类?看一下.Width和.Height的实现,我看到他们在GDI +中执行函数调用(gdiplus文件中的GdipGetImageHeight和GdipGetImageWidth),这看起来相当昂贵。
通过倒退,您可以进行一次调用,而不是每次迭代。
答案 2 :(得分:3)
不是循环反转加速了事情 - 事实上你访问宽度和高度属性的次数要少得多。
答案 3 :(得分:1)
这是因为CPU就像曲棍球运动员一样,后退时会更快; - )
更严重:
这与循环的方向无关,而是与原始构造中的循环控制条件暗示取消引用Img对象以索引其宽度或高度属性(为每个和循环中的单次迭代),其中第二个构造仅评估这些属性一次
此外,新条件针对值0进行测试的事实甚至可以节省立即值的加载。
这可能解释了差异(假设在内部完成的工作相对最小,即+/-与测试Object.Property的工作相同,因为您指示大约50%的增益)。
修改强>
请参阅Michael Stum的回答,其中指出 Img.Width / Height参考比想象的更昂贵。由于它有时会发生属性,对象的实现可能会运行大量的代码来生成值(例如,每次都可以做一堆数学来达到宽度,而不是以某种方式缓存它等等。 )。这个Img对象似乎就是这种情况,因此有兴趣只执行一次(如果你确定该值在循环逻辑的持续时间内保持不变)。