2015-05-04 95 views
1

我试图优化一个功能,占用大量的执行时间,它会多次计算下面的数学运算。无论如何要让这个操作更快?如何优化这个数学运算的速度

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)) 
                 )); 
+0

是C还是C++? – Eregrith

+1

这是C++我的不好。 – bakalolo

+5

如何使用此功能?你真的需要sqrt,或者正方形适合你吗?或者如果它在一个循环中,你可能会得到向量化的循环。 – Petr

回答

-1

通常,您希望避免使用传统的几何图形和三角函数,只要有意义就切换到矢量运算。例如这意味着以平方长度而不是长度来工作。许多使用长度的算法可以很容易地修改,以便使用平方长度。但是,如果您必须采取平方根,我会建议在您的情况下尝试使用sqrt(x*x + y*y)的专用函数hypot(x,y)(在这里,您必须调用它两次:例如hypot(x,hypot(y,z)))。这可能或不会帮助。

另外,还要考虑sqrtf代替sqrt,并接通编译器优化更快数学(例如-ffast-mathgcc)或优化(或不同的库),该牺牲精度速度。

+0

你确定一个sqrt比两次下降更慢吗? – Petr

+3

我觉得这个答案很混乱......使用矢量微积分与你是否使用欧几里得范数或平方欧几里德范数无关。 – cfh

+0

关于sqrtf parf,请注意,在C++中,sqrt函数对于浮点数,双精度浮点数和长双精度浮点数都是重载的,所以当参数为浮点数时,我没有理由明确要求sqrtf。 – Petr

2

如果内存很便宜,那么您可以执行以下操作,从而提高命中率CPU cache。既然你没有发布更多的细节,所以我会在这里做一些假设。

long tmp_len_square[N*3]; 

for (int j = 0; j < N; ++j) { 
    tmp_len_square[3 * j] = (point_A[j].length)*(point_A[j].length); 
} 

for (int j = 0; j < N; ++j) { 
    tmp_len_square[(3 * j) + 1] = (point_B[j].width)*(point_B[j].width); 
} 

for (int j = 0; j < N; ++j) { 
    tmp_len_square[(3 * j) + 2] = (point_C[j].height)*(point_C[j].height); 
} 

for (int j = 0; j < N; ++j) { 
    float total = sqrt(tmp_len_square[3 * j] + 
         tmp_len_square[(3 * j) + 1] + 
         tmp_len_square[(3 * j) + 2]); 
    // ... 
} 
+0

为什么这是一个long'long tmp_len_square [N * 3];' – tejas

+0

我后来改为'long',但这只是一个例子,实际的数据类型取决于作者想要使用的分辨率。 – Neeraj

+0

我的意思是,源类型是float,并且你把它变成(3?)long(s)a并且占用多长时间的sqrt?那不会是不确定的吗? – tejas

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的GCC和-fno-math-errno -ftree-vectorize可以矢量化该循环。尽管如此,它的确有很多的问题。 __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)

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的并行循环中受益 - 请查看手册。

现在测量您的绩效变化(假设您测量了原始数据,因为这是一个优化问题)。如果它仍然不够快,那么就该考虑更改数据结构,算法或各行代码。