【问题标题】:How do you iterate simultaneously two array that are not equally spaced in a optimized way?您如何以优化的方式同时迭代两个不等间距的数组?
【发布时间】:2019-03-30 21:37:55
【问题描述】:

假设我必须将两个数组相乘,例如 A[MAX_BUFFER]B[MAX_BUFFER](带有 MAX_BUFFER = 256)。

由于某种原因,每个B[MAX_BUFFER] 值都以固定控制率(例如8)计算,因为每个值都会被大量处理。

稍后,考虑到(引入的)不同间距,我需要将彼此相乘到C[MAX_BUFFER]。因此,A 有 256 个值,我将得到一个大小可变的 B(本例中为 32,因为控制率为 8)。

这是example code

#include <iostream>
#include <math.h>

#define MAX_BUFFER 256

double HeavyFunction(double value) {
    if (value == 0) return 0.0;

    return pow(10.0, value); // heavy operations on value...
}

int main()
{    
    int blockSize = 256;
    int controlRate = 8;

    double A[MAX_BUFFER];
    double B[MAX_BUFFER];
    double C[MAX_BUFFER];

    // fill A
    for (int sampleIndex = 0; sampleIndex < blockSize; sampleIndex++) {
        A[sampleIndex] = sampleIndex;
    }

    // fill B (control rated)
    int index = 0;
    for (int sampleIndex = 0; sampleIndex < blockSize; sampleIndex += controlRate, index++) {
        B[index] = HeavyFunction(index);
    }

    // calculate C
    for (int sampleIndex = 0; sampleIndex < blockSize; sampleIndex++) {     
        C[sampleIndex] = A[sampleIndex] + B[sampleIndex / 8];

        std::cout << C[sampleIndex] << std::endl;
    }
}

我需要性能,因为我将并行处理许多这些操作,在 1 秒内发送许多数据(例如 44100 个样本拆分为 blockSize MAX_BUFFER)。

我想避免分支(即if)和除法(如上例所示),它们不是类似 CPU 的操作(处理大量数据)。

在之前的例子中,这里会引入sampleIndex / 8 * N“徒劳”的N操作;如果我为数百万个样本调用该程序...

您将如何以一种花哨且轻松的方式为 CPU 重构此代码?

【问题讨论】:

  • 任何体面的优化编译器都会用右移替换/8。查看在担心数组索引“开销”之前生成的程序集,很可能根本没有。
  • 听起来您不需要这种级别的优化(除非您有严格的硬件限制,否则在 1 秒内天真地执行 44100 次乘法是微不足道的),但您可能会从使用 SSE/AVX 中受益更多执行操作的说明,而不是您担心分支。
  • @markzzz 看generated assembly code,没有8除,除以移位,看sar eax, 3指令。
  • 为什么 HeavyFunction(0) = 0 ?
  • 这只是一个例子,不用担心:)

标签: c++ optimization branch-prediction


【解决方案1】:

我认为优化器可能会单独完成这项工作,但您可以展开循环以避免除法:

// calculate C
const max = blockSize / 8;
int j = 0;
for (int i = 0; i != max; ++i) {
    const auto b = B[i];
    C[j] = A[j] + b; std::cout << C[j] << std::endl; ++j;
    C[j] = A[j] + b; std::cout << C[j] << std::endl; ++j;
    C[j] = A[j] + b; std::cout << C[j] << std::endl; ++j;
    C[j] = A[j] + b; std::cout << C[j] << std::endl; ++j;
    C[j] = A[j] + b; std::cout << C[j] << std::endl; ++j;
    C[j] = A[j] + b; std::cout << C[j] << std::endl; ++j;
    C[j] = A[j] + b; std::cout << C[j] << std::endl; ++j;
    C[j] = A[j] + b; std::cout << C[j] << std::endl; ++j;
}

【讨论】:

  • 两个问题: 1- 整数除法,这将是一个转变,是你要在这里优化的最后一件事。 2 - 编译器不会优化从 A 和 B 读取或写入 C。鉴于我们处于多线程范例中,这样做可能会导致副作用。因此,如果您需要 8 次,您真的想将 B[i] 保存在本地 const 变量中。
  • @UmNyobe:(1) 是 OP 想要的,我认为这也不是问题 ;-)。 (2) 对于多线程,我们应该与互斥锁同步或使用原子变量。现在我为B[i] 使用局部变量,如果在函数中使用可能会出现别名。
【解决方案2】:

如何以优化的方式同时迭代两个不等间距的数组?

简答:专注于HeavyFunction,避免在线程之间共享不必要的东西。

不幸的是,您的示例不符合他的问题。数组

double A[MAX_BUFFER];
double B[MAX_BUFFER];
double C[MAX_BUFFER];

仅仅通过移动堆栈指针就可以在堆栈上分配,所以你可以说它们非常类似于一个连续的数组。

即使它们不是现代缓存,它们也非常复杂,以至于通过尝试微优化最终可能会降低性能。

假设你有

BUFFER_SIZE = 1024 * 1024 * 1024;
std::vector<double> A(MAX_BUFFER);
std::vector<double> B(MAX_BUFFER);

一个很好的改进是

std::vector<double> C{A};
for (int i = 0; i < blockSize/controlRate; i++) { 
     const double b = B[i];
     int indexStart = i*controlRate;
     for(int j = 0 ; j < controlRate; ++j){
        Cprime[indexStart+j] += b;
     }

}

您读取 A 一次(以块为单位),读取 B 一次(一次两次),并且访问 C 的时间相同。

【讨论】:

  • controlRateblockSize 可能会有所不同。不能保证它们总是完全可分的。例如,如果我有blockSize 250 和控制率 8 怎么办?它会错过一些步骤...
猜你喜欢
  • 2015-08-16
  • 1970-01-01
  • 2015-05-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-06-11
  • 1970-01-01
相关资源
最近更新 更多