给定:
void foo(std::vector<int> v);
void bar()
{
std::vector<int> v = ...; // many items
foo(v);
}
在分析工具中,什么会显示为热路径?它是std::vector<T>
的复制构造函数、运行时还是操作系统?我记得在学校里(我不是C++开发人员,只是和一些人一起工作),这将复制v
,这可能需要时间。我知道这样的签名:
void foo(const std::vector<int>& v);
避免了这种可能代价高昂的复制操作。
按值复制std::vector<T>
可能会做三件事:
- 内存管理器(C++运行时或自定义分配器)在可用内存块的存储中搜索新向量。如果它能找到它,那么它进入步骤#3
- 内存管理器(C++运行时或自定义分配器)从操作系统请求更多内存。这个调用本身相对便宜(与系统调用一样多),因为操作系统在延迟模式下提供内存——实际分配发生在对请求的VM页面的第一次写入时
- 编译器生成的代码(应用程序)为新向量中的每个元素替换
new
,如果T
不是一般的可复制构造的。否则,调用memcpy()
或其优化的矢量化(在sse
意义上)对应物。就在第一次写入新存储器之前,如果它是从#2中的OS获取的,则OS将需要实际分配新的VM页面,并且这可能触发RAM访问(硬件)、TLB查找(硬件+OS)交换入/出(OS)
在您的特定示例中,T
通常是可复制构造的,因此最坏的情况开销是C++
运行时内存块查找+sbrk()
系统调用+memcpy()
调用。
决定按值还是按常量引用接受参数时,应考虑函数对该值的处理计划。如果函数只计划读取现有值,那么const引用是最有效的选择。如果被调用的函数在其实现中制作了引用对象的副本,那么传递值可能会更快,因为调用方可以在有意义的情况下移动构造参数,并消除对副本的需要。
void foo(std::vector<int> v);
void bar()
{
std::vector<int> v = ...; // many items
foo(std::move(v)); // no copy needed here
}
您可以期望调用std::vector<int>
的复制构造函数。
如果一个聪明的编译器这样做绝对没有副作用,那么它可以优化值副本。此外,如果函数定义标记为inline
,那么如果编译器满足您的请求(不必这样做),则不会进行值副本。
您最好的选择确实是通过const
参考v
。这不仅避免了值复制,而且意味着被调用的函数不能修改传递的参数。
编译器只需发出执行as-if
的代码即可运行代码,也就是说,可观察的行为是相同的。
可能的热点应该是bar
中的赋值运算符,因为您需要创建原始向量或复制构造函数。
在您的示例中,编译器可以用不同的方式重写它,如果它能推断出v
再也没有在bar
中使用过,那么它可能会将调用foo(v);
实现为foo(std::move(v))
(就像前面提到的@mSChange一样)。这使得CCD_ 22在CCD_。
或者,编译器可能只在调用堆栈上创建vector
,并为自己保存一个对析构函数的调用,从而有效地使按值调用免费。