为什么没有为函数参数删除副本



我有以下代码

#include <cstdlib>
#include <vector>
#include <chrono>
#include <iostream>
static const uint64_t BENCHMARK_RUNS(1000000);
std::vector<float> vec_mul_no_ref(const std::vector<float> x,
const std::vector<float> y) {
if(x.size() != y.size()) {
throw std::runtime_error("vectors are not the same size!");
}
std::vector<float> ans(x.size());
for (size_t ii=0; ii<x.size(); ii++) {
ans[ii] = x[ii] * y[ii];
}
return ans;
}
std::vector<float> vec_mul_ref_args(const std::vector<float>& x,
const std::vector<float>& y) {
if(x.size() != y.size()) {
throw std::runtime_error("vectors are not the same size!");
}
std::vector<float> ans(x.size());
for (size_t ii=0; ii<x.size(); ii++) {
ans[ii] = x[ii] * y[ii];
}
return ans;
}
void vec_mul_all_ref(const std::vector<float>& x,
const std::vector<float>& y, std::vector<float>& ans) {
if(x.size() != y.size() || y.size() != ans.size()) {
throw std::runtime_error("vectors are not the same size!");
}
for (size_t ii=0; ii<x.size(); ii++) {
ans[ii] = x[ii] * y[ii];
}
}
void bench_vec_mul() {
size_t vec_size(10000);
std::vector<float> x(vec_size);
std::vector<float> y(vec_size);
for(size_t ii=0; ii<vec_size; ii++) {
x[ii] = (static_cast<double>(rand()) / RAND_MAX) * 100.0;
y[ii] = (static_cast<double>(rand()) / RAND_MAX) * 100.0;
}
// bench no_ref
auto start = std::chrono::steady_clock::now();
for (uint64_t ii=0; ii < BENCHMARK_RUNS; ii++) {
std::vector<float> ans = vec_mul_no_ref(x, y);
}
auto end = std::chrono::steady_clock::now();
double time = static_cast<double>(
std::chrono::duration_cast<
std::chrono::microseconds>(end-start).count());
std::cout << "Time to multiply vectors (no_ref) = " 
<< time / BENCHMARK_RUNS * 1e3 << " ns" << std::endl;
// bench ref_args
start = std::chrono::steady_clock::now();
for (uint64_t ii=0; ii < BENCHMARK_RUNS; ii++) {
std::vector<float> ans = vec_mul_ref_args(x, y);
}
end = std::chrono::steady_clock::now();
time = static_cast<double>(
std::chrono::duration_cast<
std::chrono::microseconds>(end-start).count());
std::cout << "Time to multiply vectors (ref_args) = " 
<< time / BENCHMARK_RUNS * 1e3 << " ns" << std::endl;
// bench all_ref
start = std::chrono::steady_clock::now();
for (uint64_t ii=0; ii < BENCHMARK_RUNS; ii++) {
std::vector<float> ans(x.size());
vec_mul_all_ref(x, y, ans);
}
end = std::chrono::steady_clock::now();
time = static_cast<double>(
std::chrono::duration_cast<
std::chrono::microseconds>(end-start).count());
std::cout << "Time to multiply vectors (all_ref) = " 
<< time / BENCHMARK_RUNS * 1e3 << " ns" << std::endl;
}
int main() {
bench_vec_mul();
return 0;
}

在我的笔记本电脑上(用g++ -o benchmark main.cc -std=c++17 -O3编译(,这个代码的输出示例是:

Time to multiply vectors (no_ref) = 5117.05 ns                                      
Time to multiply vectors (ref_args) = 3000.69 ns
Time to multiply vectors (all_ref) = 2996.84 ns

第二次和第三次如此相似表示正在执行返回值优化,并且正在消除该副本。我的问题是:为什么编译器不删除函数参数的副本,以便第一次与第二次匹配?将函数参数声明为const可以保证它们不会更改,因此不会意外编辑原始变量。

我有gcc(gcc(8.1.1 20180531

我的问题是:为什么编译器不删除函数参数的副本,以便第一次与第二次匹配?

因为标准不允许它们是.

Elision并不是一件刚刚发生的事情;它不是无处不在的"好像"规则的一部分。因为它会影响用户可见的行为(复制/移动构造函数可能是用户定义的代码(,所以标准必须明确指出,有时,实现可以自由地不调用它们。因此,该标准仅允许在特定情况下省略

当从参数表达式初始化参数时,只有当参数是值类型并且参数表达式是该类型的临时参数时,才允许省略。如果你想了解技术,在C++17中,这种情况下没有省略:prvalue将直接初始化参数,而不是显示临时参数,因此没有复制/移动到elide。

但这里的情况并非如此。xy不是prvalue,因此不符合省略条件。它们将被复制到这些参数中。

事实上,命名对象唯一有资格省略的时候是如果返回。即使这样,如果它是该函数的参数,它也不起作用。

Nicol解释了在您的案例中标准不允许复制省略的技术原因。

至于理由,我认为只是传递值与传递引用具有不同的生存期和所有权语义。当函数按值接收参数时,它不仅仅是说"我想要一个副本,这样我就可以修改它,并使原始副本不受影响"。它还说,"我想拥有这个对象(我的副本(,并控制它的生存期,这样它就不会在我返回之前被破坏。">

在您的程序中,vec_mul_no_refvec_mul_ref_args之间没有真正的区别。但想象一下,其他人对这些函数的称呼不同。特别是,假设我调用了vec_mul_ref_args(x, y),但不知何故,xy在另一个线程的调用中途被破坏。这将是一场数据竞赛。但是,如果我打电话给vec_mul_no_ref,那就好了。

最新更新