sdg*_*sdh 133 c++ smart-pointers shared-ptr move-semantics c++11
我一直在查看Clang源代码,我找到了这个片段:
void CompilerInstance::setInvocation(
std::shared_ptr<CompilerInvocation> Value) {
Invocation = std::move(Value);
}
Run Code Online (Sandbox Code Playgroud)
我为什么要std::move
一个std::shared_ptr
?
在共享资源上转移所有权是否有任何意义?
为什么我不这样做呢?
void CompilerInstance::setInvocation(
std::shared_ptr<CompilerInvocation> Value) {
Invocation = Value;
}
Run Code Online (Sandbox Code Playgroud)
Bo *_*son 118
通过使用move
您可以避免增加,然后立即减少共享的数量.这可能会在使用次数上为您节省一些昂贵的原子操作.
Dav*_*aim 116
我认为其他答案没有强调的一点就是速度.
std::shared_ptr
引用计数是原子的.增加或减少引用计数需要原子增量或减量.这比非原子增量/减量慢一百倍,更不用说如果我们增加和减少相同的计数器,我们就会得到确切的数字,从而浪费了大量的时间和资源.
通过移动shared_ptr
而不是复制它,我们"窃取" 原子引用计数,我们使另一个无效shared_ptr
."窃取"引用计数不是原子的,它比复制shared_ptr
(并导致原子引用增量或减量)快100倍.
请注意,此技术仅用于优化.复制它(如你所建议的)就像功能一样好.
Mr.*_*C64 57
移动操作(如移动构造函数)std::shared_ptr
很便宜,因为它们基本上是"窃取指针"(从源到目的地;更准确地说,整个状态控制块从源到目的地"被盗",包括引用计数信息) .
而是在调用原子引用计数时复制操作增加(即不仅仅是在整数数据成员上,而是在Windows上调用),这比仅仅窃取指针/状态更昂贵.std::shared_ptr
++RefCount
RefCount
InterlockedIncrement
因此,详细分析此案例的引用计数动态:
// shared_ptr<CompilerInvocation> sp;
compilerInstance.setInvocation(sp);
Run Code Online (Sandbox Code Playgroud)
如果您通过sp
值传递然后在方法中复制CompilerInstance::setInvocation
,则您具有:
shared_ptr
参数是复制构造的:ref count 原子 增量.shared_ptr
参数为数据成员:引用计数原子 增量.shared_ptr
参数被破坏:ref计数原子 减量.您有两个原子增量和一个原子减量,总共三个 原子操作.
相反,如果您通过shared_ptr
值传递参数然后std::move
在方法内部(正如在Clang的代码中正确完成),您有:
shared_ptr
参数是复制构造的:ref count 原子 增量.std::move
的shared_ptr
参数为数据成员:引用计数并没有改变!你只是在窃取指针/状态:不涉及昂贵的原子引用计数操作.shared_ptr
参数被破坏; 但是因为你在第2步中移动了,所以没有什么可以破坏,因为shared_ptr
参数不再指向任何东西了.同样,在这种情况下不会发生原子减量.底线:在这种情况下,您只得到一个 ref计数原子增量,即只有一个原子操作.
正如您所看到的,这比复制案例的两个原子增量加上一个原子减量(总共三个原子操作)要好得多.
Ste*_*eel 14
在这种情况下使用std :: move有两个原因.大多数回复都解决了速度问题,但忽略了更清楚地显示代码意图的重要问题.
对于std :: shared_ptr,std :: move明确表示指向者的所有权转移,而简单的复制操作则添加额外的所有者.当然,如果原所有者随后放弃了他们的所有权(例如允许他们的std :: shared_ptr被销毁),那么就完成了所有权的转移.
当您使用std :: move转移所有权时,显而易见的是发生了什么.如果您使用普通副本,则在您验证原始所有者是否立即放弃所有权之前,预期的操作并不明显.作为奖励,可以实现更有效的实施,因为所有权的原子转移可以避免所有者数量增加1的临时状态(以及随之而来的参考计数变化).
由于这些答案都没有提供实际的基准,我想我应该尝试提供一个。然而,我想我已经让自己比开始时更加困惑了。我试图提出一个测试,该测试可以测量shared_ptr<int>
按值传递、按引用传递、使用std::move
、对该值执行添加操作并返回结果。我使用两组测试进行了多次(一百万次)。第一组在 中添加了一个常量值shared_ptr<int>
,另一组在 [0, 10] 范围内添加了一个随机值。我认为恒定值添加将是重度优化的候选者,而随机值测试则不然。这或多或少是我所看到的,但是执行时间的极端差异使我相信此测试程序的其他因素/问题是导致执行时间差异的因素,而不是移动语义。
对于没有优化 ( -O0
),不断添加
std::move
比按值传递快约 4 倍std::move
比按引用传递稍微慢一些对于高度优化 ( -O3
),不断添加
std::move
比按值传递快70-90,000倍std::move
比引用传递略快(1-1.4 倍)对于不优化 ( -O0
),随机添加
std::move
比按值传递快 1-2 倍std::move
比按引用传递稍微慢一些对于高度优化 ( -O3
),随机添加
std::move
比按值传递快 1-1.3 倍(比没有优化稍差)std::move
本质上与引用传递相同最后,测试
#include <memory>
#include <iostream>
#include <chrono>
#include <ctime>
#include <random>
constexpr auto MAX_NUM_ITS = 1000000;
// using random values to try to cut down on massive compiler optimizations
static std::random_device RAND_DEV;
static std::mt19937 RNG(RAND_DEV());
static std::uniform_int_distribution<std::mt19937::result_type> DIST11(0,10);
void CopyPtr(std::shared_ptr<int> myInt)
{
// demonstrates that use_count increases with each copy
std::cout << "In CopyPtr: ref count = " << myInt.use_count() << std::endl;
std::shared_ptr<int> myCopyInt(myInt);
std::cout << "In CopyPtr: ref count = " << myCopyInt.use_count() << std::endl;
}
void ReferencePtr(std::shared_ptr<int>& myInt)
{
// reference count stays the same until a copy is made
std::cout << "In ReferencePtr: ref count = " << myInt.use_count() << std::endl;
std::shared_ptr<int> myCopyInt(myInt);
std::cout << "In ReferencePtr: ref count = " << myCopyInt.use_count() << std::endl;
}
void MovePtr(std::shared_ptr<int>&& myInt)
{
// demonstrates that use_count remains constant with each move
std::cout << "In MovePtr: ref count = " << myInt.use_count() << std::endl;
std::shared_ptr<int> myMovedInt(std::move(myInt));
std::cout << "In MovePtr: ref count = " << myMovedInt.use_count() << std::endl;
}
int CopyPtrFastConst(std::shared_ptr<int> myInt)
{
return 5 + *myInt;
}
int ReferencePtrFastConst(std::shared_ptr<int>& myInt)
{
return 5 + *myInt;
}
int MovePtrFastConst(std::shared_ptr<int>&& myInt)
{
return 5 + *myInt;
}
int CopyPtrFastRand(std::shared_ptr<int> myInt)
{
return DIST11(RNG) + *myInt;
}
int ReferencePtrFastRand(std::shared_ptr<int>& myInt)
{
return DIST11(RNG) + *myInt;
}
int MovePtrFastRand(std::shared_ptr<int>&& myInt)
{
return DIST11(RNG) + *myInt;
}
void RunConstantFunctions(std::shared_ptr<int> myInt)
{
std::cout << "\nIn constant funcs, ref count = " << myInt.use_count() << std::endl;
// demonstrates speed of each function
int sum = 0;
// Copy pointer
auto start = std::chrono::steady_clock::now();
for (auto i=0; i<MAX_NUM_ITS; i++)
{
sum += CopyPtrFastConst(myInt);
}
auto end = std::chrono::steady_clock::now();
std::chrono::duration<double> copyElapsed = end - start;
std::cout << "CopyPtrConst sum = " << sum << ", took " << copyElapsed.count() << " seconds.\n";
// pass pointer by reference
sum = 0;
start = std::chrono::steady_clock::now();
for (auto i=0; i<MAX_NUM_ITS; i++)
{
sum += ReferencePtrFastConst(myInt);
}
end = std::chrono::steady_clock::now();
std::chrono::duration<double> refElapsed = end - start;
std::cout << "ReferencePtrConst sum = " << sum << ", took " << refElapsed.count() << " seconds.\n";
// pass pointer using std::move
sum = 0;
start = std::chrono::steady_clock::now();
for (auto i=0; i<MAX_NUM_ITS; i++)
{
sum += MovePtrFastConst(std::move(myInt));
}
end = std::chrono::steady_clock::now();
std::chrono::duration<double> moveElapsed = end - start;
std::cout << "MovePtrConst sum = " << sum << ", took " << moveElapsed.count() <<
" seconds.\n";
std::cout << "std::move vs pass by value: " << copyElapsed / moveElapsed << " times faster.\n";
std::cout << "std::move vs pass by ref: " << refElapsed / moveElapsed << " times faster.\n";
}
void RunRandomFunctions(std::shared_ptr<int> myInt)
{
std::cout << "\nIn random funcs, ref count = " << myInt.use_count() << std::endl;
// demonstrates speed of each function
int sum = 0;
// Copy pointer
auto start = std::chrono::steady_clock::now();
for (auto i=0; i<MAX_NUM_ITS; i++)
{
sum += CopyPtrFastRand(myInt);
}
auto end = std::chrono::steady_clock::now();
std::chrono::duration<double> copyElapsed = end - start;
std::cout << "CopyPtrRand sum = " << sum << ", took " << copyElapsed.count() << " seconds.\n";
// pass pointer by reference
sum = 0;
start = std::chrono::steady_clock::now();
for (auto i=0; i<MAX_NUM_ITS; i++)
{
sum += ReferencePtrFastRand(myInt);
}
end = std::chrono::steady_clock::now();
std::chrono::duration<double> refElapsed = end - start;
std::cout << "ReferencePtrRand sum = " << sum << ", took " << refElapsed.count() << " seconds.\n";
// pass pointer using std::move
sum = 0;
start = std::chrono::steady_clock::now();
for (auto i=0; i<MAX_NUM_ITS; i++)
{
sum += MovePtrFastRand(std::move(myInt));
}
end = std::chrono::steady_clock::now();
std::chrono::duration<double> moveElapsed = end - start;
std::cout << "MovePtrRand sum = " << sum << ", took " << moveElapsed.count() <<
" seconds.\n";
std::cout << "std::move vs pass by value: " << copyElapsed / moveElapsed << " times faster.\n";
std::cout << "std::move vs pass by ref: " << refElapsed / moveElapsed << " times faster.\n";
}
int main()
{
// demonstrates how use counts are effected between copy and move
std::shared_ptr<int> myInt = std::make_shared<int>(5);
std::cout << "In main: ref count = " << myInt.use_count() << std::endl;
CopyPtr(myInt);
std::cout << "In main: ref count = " << myInt.use_count() << std::endl;
ReferencePtr(myInt);
std::cout << "In main: ref count = " << myInt.use_count() << std::endl;
MovePtr(std::move(myInt));
std::cout << "In main: ref count = " << myInt.use_count() << std::endl;
// since myInt was moved to MovePtr and fell out of scope on return (was destroyed),
// we have to reinitialize myInt
myInt.reset();
myInt = std::make_shared<int>(5);
RunConstantFunctions(myInt);
RunRandomFunctions(myInt);
return 0;
}
Run Code Online (Sandbox Code Playgroud)
我注意到对于-O0
和-O3
,常量函数都编译为两组标志的相同程序集,都是相对较短的块。这让我认为大部分优化来自调用代码,但我在我的业余汇编知识中并没有真正看到这一点。
随机函数编译成相当多的汇编,即使对于-O3
,因此随机部分必须主导该例程。
所以最后,不太确定该怎么做。请向它扔飞镖,告诉我我做错了什么,并提供一些解释。
归档时间: |
|
查看次数: |
28978 次 |
最近记录: |