对几乎相同的代码执行不同的向量求和
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] 其余部分似乎在统计上占主导地位,正如更多迭代所见。因此,唯一的问题确实是通过引用调用!
完整的测试代码(用标志 -O3
和 g++
编译,也用 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 的指针。
当使用 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] 其余部分似乎在统计上占主导地位,正如更多迭代所见。因此,唯一的问题确实是通过引用调用!
完整的测试代码(用标志 -O3
和 g++
编译,也用 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 的指针。