【问题标题】:Loop interchange versus Loop tiling循环交换与循环平铺
【发布时间】:2013-09-26 16:41:15
【问题描述】:

这些优化中哪一个更好,在什么情况下?为什么?

直觉上,我感觉循环平铺通常会 做一个更好的优化。

下面的例子呢? 假设一个缓存在任何时候只能存储大约 20 个元素。

Original Loop:
for(int i = 0; i < 10; i++)
{
    for(int j = 0; j < 1000; j++)
    {
        a[i] += a[i]*b[j];
    }
}

Loop Interchange:
for(int i = 0; i < 1000; i++)
{
    for(int j = 0; j < 10; j++)
    {
        a[j] += a[j]*b[i];
    }
}

Loop Tiling:
for(int k = 0; k < 1000; k += 20)
{
    for(int i = 0; i < 10; i++)
    {
        for(int j = k; j < min(1000, k+20); j++)
        {
            a[i] += a[i]*b[j];
        }
    }
}

【问题讨论】:

  • 我怀疑这在很大程度上取决于您的数据集的大小。如果您的数据集相对较小(即可以完全放入缓存中),则平铺没有多大意义。
  • 确实如此。我正在考虑一个缓存大小非常低的假设情况(假设一个缓存在任何时候只能存储大约 20 个元素)。

标签: c performance loops caching optimization


【解决方案1】:

您在问题中公开的前两个案例大致相同。在以下两种情况下情况会真正改变:

案例 1:

for(int i = 0; i < 10; i++)
{
    for(int j = 0; j < 1000; j++)
    {
        b[i] += a[i]*a[j];
    }
}

在这里,您正在访问矩阵“a”,如下所示:a[0]*a[0], a[0]*a1, a[0]*a[2],.... 在大多数体系结构,矩阵结构存储在内存中,如:a[0]*a[0]、a1*a[0]、a[2]*a[0](第一行的第一列,然后是第二列第一个原始的,....)。想象一下,您的缓存只能存储 5 个元素,而您的矩阵是 6x6。将存储在缓存中的第一个“包”元素将是 a[0]*a[0] 到 a[4]*a[0]。您的第一次访问不会导致缓存未命中,因此 a[0][0] 存储在缓存中,但第二次是! a0 未存储在缓存中!然后操作系统会将元素包 a0 缓存到 a4。然后你进行第三次访问: a[0]*a[2] 再次超出缓存。另一个缓存未命中!

如您所见,案例 1 不是解决问题的好方法。它会导致很多缓存未命中,我们可以避免更改以下代码:

案例 2:

for(int i = 0; i < 10; i++)
    {
        for(int j = 0; j < 1000; j++)
        {
            b[i] += a[i]*a[j];
        }
    }

在这里,正如您所见,我们正在访问存储在内存中的矩阵。因此,它比案例 1 更好(更快)。

关于您发布的关于循环平铺、循环平铺和循环展开的第三个代码是在大多数情况下编译器自动执行的优化。 Here's a very interesting post in stackoverflow explaining these two techniques;

希望对您有所帮助! (对不起我的英语,我不是母语)

【讨论】:

  • 不应该是 a[0][0] 后跟 a[0][1]、a[0][2] 等等...关于存储在内存中的要点?
  • 嗯,元素在内存中的存储方式实际上取决于您使用的系统的体系结构。正如您在评论中提到的那样,在某些架构中存储元素。但是,如果人们了解他们的应用程序将在其中运行的架构,这对于理解人们应该如何编写循环的一般概念并不重要。
  • 我的回答对你有帮助吗?
猜你喜欢
  • 2011-07-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-12-20
  • 1970-01-01
  • 2019-10-15
  • 1970-01-01
相关资源
最近更新 更多