如何针对速度优化此数学运算

时间:2015-05-04 07:37:09

标签: c++ optimization

我正在尝试优化一个占用大量执行时间的函数,这会多次计算以下数学运算。反正有没有让这个操作更快?

float total = (sqrt(
          ((point_A[j].length)*(point_A[j].length))+
          ((point_B[j].width)*(point_B[j].width))+
          ((point_C[j].height)*(point_C[j].height))
                                                        ));

5 个答案:

答案 0 :(得分:2)

如果内存便宜,那么您可以执行以下操作,从而提高CPU cache命中率。由于您尚未发布更多详细信息,因此我将在此处做出一些假设。

> data %>% group_by(b) %>% slice(which.min(c))
#Source: local data frame [4 x 4]
#Groups: b
#
#   a b   c     d
#1  1 a 1.2 small
#2  4 b 1.7  larg
#3  6 c 3.1   med
#4 10 d 2.2   med

答案 1 :(得分:2)

将数据重新排列为:

float *pointA_length;
float *pointB_width;
float *pointC_height;

这可能需要对您的数据结构进行一定程度的屠杀,因此您必须选择是否值得。

现在我们能做的就是写下这个:

void process_points(float* Alengths, float* Bwidths, float* Cheights,
                    float* output, int n)
{
    for (int i = 0; i < n; i++) {
       output[i] = sqrt(Alengths[i] * Alengths[i] +
                        Bwidths[i]  * Bwidths[i]  +
                        Cheights[i] * Cheights[i]);
  }
}

这样写它可以自动矢量化。例如,针对AVX和-fno-math-errno -ftree-vectorize的GCC可以对该循环进行矢量化。尽管如此,它确实很有用。 __restrict__和对齐属性只能改善一点。所以这里也是一个手工矢量版本:(未经测试)

void process_points(float* Alengths,
                    float* Bwidths,
                    float* Cheights,
                    float* output, int n)
{
    for (int i = 0; i < n; i += 8) {
        __m256 a = _mm256_load_ps(Alengths + i);
        __m256 b = _mm256_load_ps(Bwidths + i);
        __m256 c = _mm256_load_ps(Cheights + i);
        __m256 asq = _mm256_mul_ps(a, a);
        __m256 sum = _mm256_fmadd_ps(c, c, _mm256_fmadd_ps(b, b, asq));
        __m256 hsum = _mm256_mul_ps(sum, _mm256_set1_ps(0.5f));
        __m256 invsqrt = _mm256_rsqrt_ps(sum);
        __m256 s = _mm256_mul_ps(invsqrt, invsqrt);
        invsqrt = _mm256_mul_ps(sum, _mm256_fnmadd_ps(hsum, s, _mm256_set1_ps(1.5f)));
        _mm256_store_ps(output + i, _mm256_mul_ps(sum, invsqrt));
    }
}

这有很多假设:

  • 所有指针都是32对齐的。
  • n是8的倍数,或者至少缓冲区有足够的填充,它们永远不会超出范围。
  • 输入缓冲区没有输出缓冲区别名(它们之间可能是别名,但是..为什么)
  • 以这种方式计算的平方根的精确度稍微降低就可以了(精确到大约22位,而不是正确舍入)。
  • 使用fmadd计算的平方和可能与使用乘法和加法计算的方格略有不同,我认为也可以
  • 您的目标支持AVX / FMA,因此实际上会运行

我在这里使用的计算平方根的方法是使用近似倒数平方根,改进步骤(y = y * (1.5 - (0.5 * x * y * y))),然后乘以x,因为x * 1/sqrt(x) = x/sqrt(x) = sqrt(x)

答案 2 :(得分:1)

您最终可以尝试优化sqrt函数本身。我建议你看一下这个链接: Best Square Root Method

答案 3 :(得分:1)

通过添加更多上下文可以改善您的问题。您的代码是可移植的,还是针对特定的编译器或特定的处理器或处理器系列?您是否愿意接受在运行时选择特定于目标的优化版本的通用基线版本?

此外,您提供的代码行的上下文非常少。它是在一个紧凑的循环?或者它是否在这样的循环中分散在条件代码中的一堆地方?

我将假设它处于紧密循环中:

for (int j=0;  j<total;  ++j)
    length[j] = sqrt(
      (point_A[j].length)*(point_A[j].length) +
      (point_B[j].width)*(point_B[j].width) +
      (point_C[j].height)*(point_C[j].height));

我还假设您的目标处理器是多核的,并且阵列是不同的(或相关元素是不同的),那么轻松获胜就是为OpenMP注释:

#pragma omp parallel for
for (int j=0;  j<total;  ++j)
    length[j] = sqrt((point_A[j].length)*(point_A[j].length) +
                     (point_B[j].width)*(point_B[j].width) +
                     (point_C[j].height)*(point_C[j].height));

使用g++ -O3 -fopenmp -march=native进行编译(或用您期望的目标处理器架构替换native)。

如果你知道你的目标,你可能会从gcc标志-ftree-parallelize-loops=n的循环并行化中受益 - 请参阅手册。

现在衡量你的表现变化(我假设你测量了原始数据,假设这是一个优化问题)。如果它仍然不够快,那么现在是时候考虑更改数据结构,算法或单独的代码行。

答案 4 :(得分:-2)

通常,您希望避免使用传统几何和三角学,而是在有意义时切换到矢量微积分。例如这意味着使用平方长度而不仅仅是长度。许多使用长度的算法都可以很容易地修改,以便使用平方长度。

但是如果你必须采取平方根,我建议在你的情况下尝试编译def invite inviteUser = { 'user_id' => User.find_by_email('user@example.com').id, 'Magazine_id' => params[:id] } CollaborationInvitation.create(inviteUser) @magazine = Magazine.find(params[:id]) redirect_to :back rescue ActionController::RedirectBackError redirect_to root_path end 的专用函数hypot(x,y)(在这里,你必须调用它两次:例如{{1 }})。这可能有所帮助,也可能没有帮助。

另外,考虑sqrtf而不是sqrt(x*x + y*y),并启用编译器优化以加快数学运算(例如hypot(x,hypot(y,z)) sqrt)或优化(或不同的库)牺牲精确的速度。