解释两种几乎相同的算法的性能差异

Explaining performance difference in two nearly identical algorithms

本文关键字:算法 性能 两种 解释      更新时间:2023-10-16

这个问题很模糊,我真的不需要答案,但我很好奇答案是什么,所以我还是会问的。

我有一个生成大量矩阵的算法。它随后在上面运行第二个算法,生成一个解决方案。我跑了100次,平均耗时约17秒。

第二种算法几乎完全相同,唯一的区别是,第二种方法在每个矩阵生成后立即运行,因此它们实际上不需要存储在任何地方。这个变体显然需要更少的空间,这就是我制作它的原因,但对于同样的问题,它平均只需要大约2秒。

我没想到它会跑得更快,尤其是没那么快。

代码相当大,所以我将尝试概述类似伪代码的区别:

recursiveFill(vector<Matrix> &cache, Matrix permutation) {
  while(!stopCondition) {
    // generate next matrix from current permutation
    if(success)
      cache.push_back(permutation);
    else
      recursiveFill(cache, permutation);
    // some more code
  }
}
recursiveCheck(Matrix permutation) {
  while(!stopCondition) {
    // alter the matrix some
    if(success)
      checkAlgorithm(permutation);
    else
      recursiveCheck(permutation);
    // some more code
  }
}

在递归填充之后,一个循环在缓存中的所有元素上运行checkAlgorithm。我没有包含在代码中的所有内容在两种算法中都是相同的。我猜向量中的存储一直在消耗,但如果我没有记错的话,每次过满时,c++向量的大小都会翻倍,所以重新分配不应该经常发生。有什么想法吗?

我想额外的时间是由于vector中矩阵的复制。根据您给出的时间,一次通过数据需要20或170毫秒,这对于大量复制来说是正确的数量级。

请记住,即使由于向量的重新分配而导致的复制开销是线性的,但每个插入的矩阵平均会复制两次,一次在插入期间,另一次在重新分配期间。再加上复制大量数据的缓存破坏效应,这可能会产生额外的运行时。

现在你可能会说:但当我把矩阵传递给递归调用时,我也在复制矩阵,难道我不应该期望第一个算法的时间最多是第二个算法的三倍吗
答案是,如果不受堆上数据缓存利用率的阻碍,那么任何递归的体面都是完全友好的缓存。因此,在递归体面中完成的几乎所有复制甚至都没有到达L2高速缓存。如果您通过vector重新分配不时地清空整个缓存,那么之后将使用完全冷的缓存继续。

这里的罪魁祸首可能是时间局部性。您的CPU缓存只有这么大,所以当您在每次运行后保存所有内容并稍后返回时,它会在这段时间内离开CPU缓存,并且需要更长的时间(10到100秒的周期)才能访问。对于第二种方法,它就在L1(或者可能是MMX寄存器)中,只需要一两个周期就可以访问。

在优化中,你通常想像吴唐家族一样思考:缓存规则我周围的一切。

有些人对此进行了测试,缓存中的副本通常比主内存中的取消引用便宜得多。

严格来说,vector不必每次增长两倍,它只需要几何增长即可提供所需的摊余恒定时间。

在这种情况下,如果您有足够多的矩阵,那么增长和所需的数据拷贝仍然可能是问题所在。或者可以通过交换来分配足够的内存。唯一可以确定的方法是在您遇到这种差异的系统上配置