【问题标题】:optimization math computation (multiplication and summing)优化数学计算(乘法和求和)
【发布时间】:2010-05-15 15:50:55
【问题描述】:

假设您要计算项目差异的平方和:

$\sum_{i=1}^{N-1} (x_i - x_{i+1})^2$

最简单的代码(输入为std::vector<double> xs,输出为sum2)为:

double sum2 = 0.;
double prev = xs[0];
for (vector::const_iterator i = xs.begin() + 1;
 i != xs.end(); ++i)
{
sum2 += (prev - (*i)) * (prev - (*i)); // only 1 - with compiler optimization
prev = (*i);
}

我希望编译器在上面的评论中做优化。如果Nxs 的长度,则有N-1 乘法和2N-3(sums 表示+-)。

现在假设你知道这个变量:

$x_1^2 + x_N^2 + 2 \sum_{i=2}^{N-1} x_i^2$

并称之为sum。展开二项式平方:

$sum_i^{N-1} (x_i-x_{i+1})^2 = sum - 2\sum_{i=1}^{N-1} x_i x_{i+1}$

所以代码变成:

double sum2 = 0.;
double prev = xs[0];
for (vector::const_iterator i = xs.begin() + 1;
 i != xs.end(); ++i)
{
sum2 += (*i) * prev;
prev = (*i);
}
sum2 = -sum2 * 2. + sum;

这里我有 N 次乘法和 N-1 次加法。在我的例子中,N 大约是 100。

好吧,用g++ -O2编译我没有加速(我尝试调用内联函数2M次),为什么?

【问题讨论】:

  • 请尝试正确格式化 LaTex 内容。所以支持它。
  • 不知何故我怀疑矩阵运算可能会有所帮助。尝试使用 GPU 或对其进行矢量化 :)
  • @Hamish1:你确定吗?如何? @Hamish2:操作的矢量化应该会有所帮助,但现在我只考虑数学计算的简化。
  • 您可以尝试查看 gcc 发出的汇编代码,看看它做了哪些优化。 (有一个编译器开关)。胡乱猜测,也许添加的不是你程序的性能瓶颈,例如因为乘法要贵得多?
  • @Hamish:这是正确的 LaTex,但 stackoverflow 不支持它。我认为 mathoverflow 确实...

标签: c++ optimization math floating-point formula


【解决方案1】:

就执行时间而言,乘法比加法要昂贵得多。此外,根据处理器的不同,加法和乘法将并行完成。 IE。它将在进行加法时开始下一次乘法(请参阅http://en.wikipedia.org/wiki/Out-of-order_execution)。

因此减少添加的数量对性能没有太大帮助。

您可以做的是让编译器更容易向量化您的代码,或者自己进行向量化。 为了让编译器更容易向量化,我会使用常规的双精度数组,使用下标而不是指针。

编辑:N = 100 也可能是一个很小的数字,以查看执行时间的差异。尝试一个更大的 N。

脏代码但显示性能改进。输出:

1e+06
59031558
1e+06
18710703

您获得的加速约为 3 倍。

#include <vector>
#include <iostream>

using namespace std;

unsigned long long int rdtsc(void)
{
  unsigned long long int x;
  unsigned a, d;

  __asm__ volatile("rdtsc" : "=a" (a), "=d" (d));

  return ((unsigned long long)a) | (((unsigned long long)d) << 32);;
}



double f(std::vector<double>& xs)
{
  double sum2 = 0.;
  double prev = xs[0];

  vector<double>::const_iterator iend = xs.end();
  for (vector<double>::const_iterator i = xs.begin() + 1;
       i != iend; ++i)
    {
      sum2 += (prev - (*i)) * (prev - (*i)); // only 1 - with compiler optimization
      prev = (*i);
    }

  return sum2;
}

double f2(double *xs, int N)
{
  double sum2 = 0;

  for(int i = 0; i < N - 1; i+=1) {
    sum2 += (xs[i+1] - xs[i])*(xs[i+1] - xs[i]);

  }

  return sum2;
}

int main(int argc, char* argv[])
{
  int N = 1000001;
  std::vector<double> xs;
  for(int i=0; i<N; i++) {
    xs.push_back(i);
  }

  unsigned long long int a, b;
  a = rdtsc();
  std::cout << f(xs) << endl;
  b = rdtsc();
  cout << b - a << endl;

  a = rdtsc();
  std::cout << f2(&xs[0], N) << endl;
  b = rdtsc();
  cout << b - a << endl;
}

【讨论】:

  • 对。但如果可以的话,让 N 更大更容易检查加速。
  • a) 为什么使用 i+=1 而不是 ++i? b) 加速是因为您使用的是普通数组而不是 std::vector 或者因为您没有使用 prev 变量?
  • a) 因为我尝试了循环展开并且 i+=4。它并没有提供更多的加速。 b)加速是因为我主要使用普通数组(您可以尝试删除示例中的 prev)。此外,如果您使用标准构造,编译器会做得最好(尽管这不是一般规则)。如果可能,您应该尝试 ICC(英特尔 C 编译器)。有一个免费的版本供个人使用,它是一个非常好的循环分析。
  • with g++ -O2 我没有得到你的改进:1e+06->7422819, 1e+06->7186527
  • 有趣。我使用了没有标志的 g++,通常是 g++ -O2。你在什么架构上运行?
【解决方案2】:

当 x+=a*b 完成时,加法是免费的。如果架构支持,编译器应该能够在第一个版本中解决这个问题。

数学可能与 *i 并行发生,这可能会更慢。

不要在每次循环迭代时调用xs.end(),除非您希望返回值发生变化。如果编译器无法对其进行优化,它将使循环的其余部分相形见绌。

【讨论】:

    猜你喜欢
    • 2015-08-19
    • 2019-04-11
    • 2012-04-11
    • 1970-01-01
    • 1970-01-01
    • 2023-04-11
    • 2011-03-21
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多