关于执行 std::string 步
On the implementation of std::string moves
我正在调查移动 std::string
的性能。很长一段时间以来,我一直认为字符串移动几乎是免费的,认为编译器会内联所有内容,并且只会涉及一些廉价的分配。
事实上,我的搬家心智模型就是
string& operator=(string&& rhs) noexcept
{
swap(*this, rhs);
return *this;
}
friend void swap(string& x, string& y) noexcept
{
// for disposition only
unsigned char buf[sizeof(string)];
memcpy(buf, &x, sizeof(string));
memcpy(&x, &y, sizeof(string));
memcpy(&y, buf, sizeof(string));
}
据我所知,如果 memcpy
更改为分配单个字段,这是合法的实施。
我很惊讶地发现 gcc 的移动实现涉及 creating a new string and might possibly throw due to the allocations despite being noexcept
。
这还合规吗?同样重要的是,我不应该认为搬家几乎是免费的吗?
令人困惑的是,std::vector<char>
compiles down 符合我的预期。
clang's implementation就大不一样了,虽然有疑点std::string::reserve
不完全是一个答案,但这是 C++11 std::string
的新实现,没有引用计数器和小字符串优化,导致大量汇编。特别是,小字符串优化导致 4 个分支处理移动分配的源和目标长度的 4 种不同组合。
当添加 -D_GLIBCXX_USE_CXX11_ABI=0
选项以使用带有引用计数器的预 C++-11 std::string
并且没有小字符串优化时 the assembly code looks much better。
should I not think moving is almost free?
在 Nothing is Better than Copy or Move by Roger Orr talk, slides 第 47 页中说:
Comparison of copy and move
- Many people incorrectly think of move as effectively 'free'
- The performance difference between copy and move varies widely
- For a primitive type, such as int, copying or moving are effectively identical
- Move is faster than copy when only part of the object needs to be transferred to transfer the whole value
我只分析了GCC的版本。事情是这样的:代码处理不同类型的分配器。如果分配器具有 _S_propagate_on_move_assign
或 _S_always_equal
的特征,那么移动几乎是免费的,如您所料。这是移动 operator=
:
中的 if
if (!__str._M_is_local()
&& (_Alloc_traits::_S_propagate_on_move_assign()
|| _Alloc_traits::_S_always_equal()))
// cheap move
else assign(__str);
如果条件为真(_M_is_local()
表示小串,说明here),则走法便宜
如果为假,则调用正常 assign
(不是移动的)。这是以下情况之一:
- 字符串很小,所以
assign
会做一个简单的memcpy(便宜)
- 或者分配器不具有 always-equal 或 propagate-on-move-assign 特征,因此分配 会分配(不便宜)
这是什么意思?
这意味着,如果您使用默认分配器(或具有前面提到的特征的任何分配器),那么移动仍然几乎是免费的。
另一方面,生成的代码不必要地庞大,我认为可以改进。它应该有一个单独的代码来处理通常的分配器,或者有一个更好的 assign
代码(问题是 assign
不检查 _M_is_local()
,但它会检查容量,所以编译器无法决定是否需要分配,因此它不必要地将分配代码路径放入可执行文件中 - 您可以在源代码中查看确切的详细信息。
我正在调查移动 std::string
的性能。很长一段时间以来,我一直认为字符串移动几乎是免费的,认为编译器会内联所有内容,并且只会涉及一些廉价的分配。
事实上,我的搬家心智模型就是
string& operator=(string&& rhs) noexcept
{
swap(*this, rhs);
return *this;
}
friend void swap(string& x, string& y) noexcept
{
// for disposition only
unsigned char buf[sizeof(string)];
memcpy(buf, &x, sizeof(string));
memcpy(&x, &y, sizeof(string));
memcpy(&y, buf, sizeof(string));
}
据我所知,如果 memcpy
更改为分配单个字段,这是合法的实施。
我很惊讶地发现 gcc 的移动实现涉及 creating a new string and might possibly throw due to the allocations despite being noexcept
。
这还合规吗?同样重要的是,我不应该认为搬家几乎是免费的吗?
令人困惑的是,std::vector<char>
compiles down 符合我的预期。
clang's implementation就大不一样了,虽然有疑点std::string::reserve
不完全是一个答案,但这是 C++11 std::string
的新实现,没有引用计数器和小字符串优化,导致大量汇编。特别是,小字符串优化导致 4 个分支处理移动分配的源和目标长度的 4 种不同组合。
当添加 -D_GLIBCXX_USE_CXX11_ABI=0
选项以使用带有引用计数器的预 C++-11 std::string
并且没有小字符串优化时 the assembly code looks much better。
should I not think moving is almost free?
在 Nothing is Better than Copy or Move by Roger Orr talk, slides 第 47 页中说:
Comparison of copy and move
- Many people incorrectly think of move as effectively 'free'
- The performance difference between copy and move varies widely
- For a primitive type, such as int, copying or moving are effectively identical
- Move is faster than copy when only part of the object needs to be transferred to transfer the whole value
我只分析了GCC的版本。事情是这样的:代码处理不同类型的分配器。如果分配器具有 _S_propagate_on_move_assign
或 _S_always_equal
的特征,那么移动几乎是免费的,如您所料。这是移动 operator=
:
if
if (!__str._M_is_local()
&& (_Alloc_traits::_S_propagate_on_move_assign()
|| _Alloc_traits::_S_always_equal()))
// cheap move
else assign(__str);
如果条件为真(_M_is_local()
表示小串,说明here),则走法便宜
如果为假,则调用正常 assign
(不是移动的)。这是以下情况之一:
- 字符串很小,所以
assign
会做一个简单的memcpy(便宜) - 或者分配器不具有 always-equal 或 propagate-on-move-assign 特征,因此分配 会分配(不便宜)
这是什么意思?
这意味着,如果您使用默认分配器(或具有前面提到的特征的任何分配器),那么移动仍然几乎是免费的。
另一方面,生成的代码不必要地庞大,我认为可以改进。它应该有一个单独的代码来处理通常的分配器,或者有一个更好的 assign
代码(问题是 assign
不检查 _M_is_local()
,但它会检查容量,所以编译器无法决定是否需要分配,因此它不必要地将分配代码路径放入可执行文件中 - 您可以在源代码中查看确切的详细信息。