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

时间:2014-04-16 18:29:14

标签: c++ performance vector benchmarking

这个问题相当含糊,而且我并非真正需要回答它,但我对答案可能是什么非常好奇,所以无论如何我都会问它

我有一个生成大量矩阵的算法。它后来运行第二个算法,生成一个解决方案。我跑了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 ++向量的大小都会翻倍,所以重新分配不应该经常发生。 有什么想法吗?

3 个答案:

答案 0 :(得分:2)

这里的罪魁祸首可能是时间地点。你的CPU缓存只是如此之大,所以当你在每次运行后保存所有内容并稍后再回来时,它会同时离开你的CPU缓存并需要更长的时间(10到100秒的周期)才能访问。使用第二种方法,它就在L1(或可能是MMX寄存器)中,并且只需要一个或两个周期来访问。

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

Some people have done testing on this,缓存中的副本通常 比取消引用主内存便宜。

答案 1 :(得分:2)

我猜这个额外的时间是由vector内的矩阵复制造成的。根据您提供的时间,一次传递数据需要20或170毫秒,这对于大量复制来说是正确的数量级。

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

现在您可能会说:但是当我将它们传递给递归调用时我也会复制矩阵,我不应该期望第一个算法最多花费三倍于第二个算法的时间吗? 答案是,如果不受堆上数据的缓存利用率的影响,任何递归都可以完全缓存友好。因此,几乎所有在递归中完成的复制甚至都没有到达L2缓存。如果您通过vector重新分配不时地破坏整个缓存,那么之后将恢复完全冷缓存。

答案 2 :(得分:0)

严格来说,vector并不需要将每次增长加倍,只需几何增长就可以提供所需的摊销常数时间。

在这种情况下,如果您有足够多的矩阵,增长和所需的数据副本仍可能成为问题。或者它可以交换分配足够的内存。确切知道的唯一方法是在您遇到这种差异的系统上 profile