我有以下代码
#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
答案 0 :(得分:5)
我的问题是:为什么编译器也没有删除函数参数的副本,以便第一次匹配后两个?
因为标准不允许他们这样做。
淘汰不是刚刚发生的事情;它不是普遍存在的假设规则的一部分。因为它影响用户可见的行为(复制/移动构造函数可能是用户定义的代码),所以该标准必须明确指出,有时实现可以自由地不调用它们。因此,该标准只允许在非常特殊的情况下省略 。
从参数表达式初始化参数时,仅当参数为值类型且参数表达式为该类型的临时类型时才允许省略。好吧,如果您想获取技术知识,在C ++ 17中不会出现这种情况:prvalue可以直接初始化参数而不是显示临时变量,因此无需复制/移动即可。
但事实并非如此。 x
和y
不是prvalue,因此不适合省略。它们将被复制到这些参数中。
事实上,命名对象唯一可以进行省略的条件是您返回。即便如此,如果它是该函数的参数,它也不起作用。
答案 1 :(得分:2)
Nicol解释了您的情况下标准不允许复制省略的技术原因。
关于基本原理,我认为只是值传递具有与引用传递不同的生存期和所有权语义。当一个函数通过值接收一个参数时,它不仅是在说“我想要一个副本,以便可以对其进行修改并使原始对象不受影响”。也是在说:“我想拥有这个对象(我的副本),并控制它的生存期,以使它在返回之前不会被破坏。”
在您的程序中,vec_mul_no_ref
和vec_mul_ref_args
之间没有真正的区别。但是,想象其他人对这些功能的调用有所不同。特别是,假设我调用了vec_mul_ref_args(x, y)
,并且以某种方式x
和y
在另一个线程中被销毁了。这将是一场数据竞赛。但是,如果我叫vec_mul_no_ref
,那就没问题了。