你能解释一下为什么以下代码(未优化)在计算时间上存在如此大的差异吗?我怀疑 RVO 与移动构造,但我不太确定。
一般来说,遇到这种情况,最佳做法是什么?在初始化非 POD 数据时,循环中的自动声明是否被认为是一种不好的做法?
在循环中使用 auto :
std::vector<int> foo()
{
return {1,2,3,4,5};
}
int main()
{
for (size_t i = 0; i < 1000000; ++i)
auto f = foo();
return 0;
}
输出:
./a.out 0.17s 用户 0.00s 系统 97% cpu 0.177 总计
循环外的向量实例:
std::vector<int> foo()
{
return {1,2,3,4,5};
}
int main()
{
std::vector<int> f;
for (size_t i = 0; i < 1000000; ++i)
f = foo();
return 0;
}
输出:
./a.out 0.32s 用户 0.00s 系统 99% cpu 0.325 总计
我怀疑 RVO 与移动构造,但我不太确定。
是的,这几乎肯定是正在发生的事情。第一种情况是从函数的返回值移动初始化一个变量:在这种情况下,可以通过让函数就地初始化它来省略移动。第二种情况从返回值开始移动赋值;作业不能省略。我相信 GCC 即使在优化级别为零时也会执行省略,除非您明确禁用它。
在最后一种情况下(使用
-O3
,现在已从问题中删除)编译器可能会注意到循环没有副作用,并将其完全删除。
您可能(或可能不会)通过声明向量
volatile
并进行优化编译来获得更有用的基准。这将迫使编译器在每次迭代中实际创建/分配它,即使它认为它知道得更多。
在初始化非 POD 数据时,循环中的自动声明是否被认为是一种不好的做法?
没有;如果有的话,在所需的最窄范围内声明事物被认为是更好的做法。所以如果只在循环中需要,就在循环中声明。在某些情况下,通过在循环外声明一个复杂的对象以避免在每次迭代中重新创建它可能会获得更好的性能;但只有当您确定性能优势 (a) 存在并且 (b) 值得失去局部性时才这样做。
我不认为您的示例与
auto
有任何关系。你写了两个不同的程序。
同时
for (size_t i = 0; i < 1000000; ++i)
auto f = foo();
相当于
for (size_t i = 0; i < 1000000; ++i)
std::vector<int> f = foo();
-- 这意味着,你 create 一个新的向量(并销毁旧的)。而且,是的,在使用 RVO 的
foo
实现中,但这不是重点:您仍然在外循环为 vector
腾出空间的地方创建一个新的 f
。
片段
std::vector<int> f;
for (size_t i = 0; i < 1000000; ++i)
f = foo();
使用 assign 到现有向量。而且,是的,对于 RVO,它可能会变成 move-assign,具体取决于
foo
,在您的情况下,所以您可以期望它很快。但它仍然 is 是另一回事——它总是 f
负责管理资源。
但是你在这里非常漂亮地展示了遵循一般规则通常是有意义的
声明变量尽可能接近它们的使用。
见这个讨论
我在我的电脑上测试了 3 个版本。手动优化版本是最快的。
constexpr size_t LOOP = 1000000000;
std::vector<int> foo() { return {1, 2, 3, 4, 5}; }
void foo_optimized(vector<int> &output) {
constexpr static int values[] = {1, 2, 3, 4, 5};
output.assign(&values[0], &values[0] + 5);
}
int main(int argc, char **argv) {
string type = string(argv[1]);
if (type == "original") {
std::vector<int> f;
for (size_t i = 0; i < LOOP; ++i)
f = foo();
} else if (type == "RVO") {
for (size_t i = 0; i < LOOP; ++i)
auto f = foo();
} else if (type == "optimized") {
std::vector<int> f;
for (size_t i = 0; i < LOOP; ++i)
foo_optimized(f);
}
return 0;
}
$ g++ a.cpp -O3
$ time ./a.out original && time ./a.out RVO && time ./a.out optimized
real 0m11.671s
user 0m11.662s
sys 0m0.000s
real 0m15.012s
user 0m15.011s
sys 0m0.000s
real 0m0.767s
user 0m0.759s
sys 0m0.000s
对于
-O2
,结果是: