关于std::string动作的实现

问题描述 投票:0回答:2

我正在调查移动

std::string
的性能。在很长一段时间里,我认为字符串移动几乎是免费的,认为编译器会内联所有内容,并且只涉及一些廉价的赋值。

事实上,我对于移动的心理模型就是字面上的

string& operator=(string&& rhs) noexcept
{
    swap(*this, rhs);
    return *this;
}

friend void swap(string& x, string& y) noexcept
{
    // exposition only
    unsigned char buf[sizeof(string)];
    memcpy(buf, &x, sizeof(string));
    memcpy(&x, &y, sizeof(string));
    memcpy(&y, buf, sizeof(string));
}

据我所知,如果将

memcpy
更改为分配各个字段,这就是合法的实现。

令我非常惊讶的是,发现 gcc 的移动实现涉及 创建一个新字符串 并且 可能会由于分配而抛出,尽管是

noexcept

这符合吗?同样重要的是,我不应该认为搬家几乎是免费的吗?


令人困惑的是,

std::vector<char>
编译下来达到了我的预期。

clang 的实现有很大不同,尽管有一个可疑的

std::string::reserve

c++ string move-semantics
2个回答
1
投票

我只分析了GCC的版本。发生的事情是这样的:代码处理不同类型的分配器。如果分配器具有

_S_propagate_on_move_assign
_S_always_equal
的特征,那么正如您所期望的,移动几乎是免费的。这是移动中的
if
operator=

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()
表示小字符串,描述这里),那么此举很便宜。

如果为 false,则调用 normal

assign
(不是移动的)。出现以下任一情况时就会出现这种情况:

  • 字符串很小,所以
    assign
    会做一个简单的memcpy(便宜)
  • 或者分配器没有特征 always-equal 也没有 propagate-on-move-assign,所以分配 will allocate (不便宜)

这是什么意思?

这意味着,如果您使用默认分配器(或任何具有前面提到的特征的分配器),那么 move 仍然几乎是免费的

另一方面,生成的代码不必要地庞大,我认为可以改进。它应该有一个单独的代码来处理常用的分配器,或者有一个更好的

assign
代码(问题是
assign
不检查
_M_is_local()
,但它会进行容量检查,因此编译器无法决定是否存在是否需要分配,因此它不必要地将分配代码路径放入可执行文件中 - 您可以在源代码中查看确切的详细信息)。


0
投票

不完全是一个答案,但这是 C++11 的新实现

std::string
,没有引用计数器,并且采用小字符串优化,导致大量汇编。特别是,小字符串优化导致 4 个分支处理移动分配的源和目标长度的 4 种不同组合。

添加

-D_GLIBCXX_USE_CXX11_ABI=0
选项以使用带有引用计数器且没有小字符串优化的 pre C++-11
std::string
汇编代码看起来好多了


我不应该认为搬家几乎是免费的吗?

在 Roger Orr 的没有什么比复制或移动更好的演讲中,幻灯片第 47 页上写道:

复制与移动对比

  • 许多人错误地认为搬家实际上是“免费”的
  • 复制和移动之间的性能差异差异很大
  • 对于原始类型,例如 int,复制或移动实际上是相同的
  • 当只需要传输对象的一部分来传输整个值时,移动比复制更快
© www.soinside.com 2019 - 2024. All rights reserved.