对几乎相同的代码执行不同的向量求和

Sum over vector performing differently for nearly identical code

当使用 g++ 标志 -O3:

编译时,这些对数组和向量求和的函数之间似乎存在性能差异
float sum1(float* v, int length) {
    float sum = 0;
    for(int i = 0; i < length; i++) {
        sum += v[i];
    }
    return sum;
}

float sum2(std::vector<float> v) {
    return sum1(&v[0], v.size());
}

当调用 sum1 时,例如一个长度为 100000 且 sum2 具有相同长度和内容的向量,sum2 最终约为。在我的测试中比 sum1 慢 10%。 测量运行次为:

sum1: 0.279816 ms
sum2: 0.307811 ms

现在这些开销从何而来?附上你还找到了完整的测试代码,以防万一我在那里犯了一个错误。

[更新] 当通过引用调用 (float sum2(std::vector<float>& v)) 时,性能差异约为。还剩 3.7%,所以这有帮助,但其他地方仍然有一些性能损失?

[Update2] 其余部分似乎在统计上占主导地位,正如更多迭代所见。因此,唯一的问题确实是通过引用调用!


完整的测试代码(用标志 -O3g++ 编译,也用 clang++ 测试):

#include <iostream>
#include <chrono>
#include <vector>

using namespace std;

std::vector<float> fill_vector(int length) {
    std::vector<float> ret;

    for(int i = 0; i < length; i++) {
        float r = static_cast <float> (rand()) / static_cast <float> (RAND_MAX);
        ret.push_back(r);
    }

    return ret;
}

float sum1(float* v, int length) {
    float sum = 0;
    for(int i = 0; i < length; i++) {
        sum += v[i];
    }
    return sum;
}

float sum2(std::vector<float> v) {
    return sum1(&v[0], v.size());
}

int main() {
    int iterations = 10000;
    int vector_size = 100000;

    srand(42);
    std::vector<float> v1 = fill_vector(vector_size);

    float* v2;
    v2 = &v1[0];

    std::chrono::duration<double, std::milli> duration_sum1(0);
    for(int i = 0; i < iterations; i++) {
        auto t1 = std::chrono::high_resolution_clock::now();
        float res = sum1(v2, vector_size);
        auto t2 = std::chrono::high_resolution_clock::now();
        cout << "Result sum1: " << res << endl;
        duration_sum1 += t2 - t1;
    }
    duration_sum1 /= iterations;

    std::chrono::duration<double, std::milli> duration_sum2(0);
    for(int i = 0; i < iterations; i++) {
        auto t1 = std::chrono::high_resolution_clock::now();
        float res = sum2(v1);
        auto t2 = std::chrono::high_resolution_clock::now();
        cout << "Result sum2: " << res << endl;
        duration_sum2 += t2 - t1;
    }
    duration_sum2 /= iterations;

    cout << "Durations:" << endl;
    cout << "sum1: " << duration_sum1.count() << " ms" << endl;
    cout << "sum2: " << duration_sum2.count() << " ms" << endl;
}

我认为开销来自传递向量。

尝试传递引用:

float sum2(std::vector<float>& v)

你的函数 sum2() 接受一个 std::vector<float> 对象按值:

float sum2(std::vector<float> v) {
    return sum1(&v[0], v.size());
}

在这样的场景中:

std::vector<float> vec;
// ...
sum2(vec); // copies vec

参数对象 v 导致参数 vec 的复制初始化传递给 sum2()。这可能是一项昂贵的操作,尤其是在向量很大的情况下。如果您的目标是降低与调用 sum2() 相关的开销,您可以选择:

  • sum2() 接受对 std::vector<float> 引用 ,即 std::vector<float>&:

    float sum2(std::vector<float>& v) {
       return sum1(&v[0], v.size());
    }
    

    在这种情况下,只是将向量的引用传递给函数,而不是整个向量,因此不会创建向量的副本。

  • 以某种方式调用 sum2(),使其参数对象 v 从传递的参数中 移动初始化(相对于copy initialized – 你现在正在做什么)如果你在调用 sum2() 之后不再需要 vec 的内容:

    sum2(std::move(vec)); // move instead of copy
    

添加到已经确定的使用引用的答案中,以避免昂贵的矢量副本:

使用引用时,您可以使用 const 引用。

你需要改变

float sum1(float* v, int length)

float sum1(const float* v, int length)

float sum2(std::vector<float> v)

float sum2(const std::vector<float>& v)

使用引用意味着您不复制矢量,但这也允许 sum2 对矢量进行更改。由于您使用引用的原因只是为了避免复制,我认为最好声明 sum2 不会更改其界面中的向量。

const 的相同逻辑适用于 sum1 并且变得无关紧要,因为 const 向量仅提供指向 const 的指针。