【问题标题】:C++ String Concatenation OptimizationsC++ 字符串连接优化
【发布时间】:2017-06-10 23:23:25
【问题描述】:

看这样一段代码(添加了cmets):

std::string some_var;
std::string some_func(); // both are defined, but definition is irrelevant
...
return "some text " + some_var + "c" + some_func(); // intentionally "c" not 'c'

我想知道,在哪些情况下 operator +std::string 必须进行复制(在使用复制构造/分配的意义上,而不是复制的内部缓冲区,例如,如果 SSO 应用),以及实际复制的内容。快速查看cppreference 只是部分有用,因为它列出了 12(!)个不同的案例。在某种程度上,我要求确认我对页面的理解:

  • 案例 1) 制作 lhs 的副本,然后将 rhs 复制到此副本的末尾
  • 在 C++98 案例 2) - 5) 中,从 char/const char* 参数构造一个临时字符串,然后导致案例 1)
  • 在 C++11 案例 2) - 5) 中,临时字符串由 char/const char* 参数,然后导致情况 6) 或 7)
  • 在 C++11 案例 6) - 12) 中,r-value 参数将被 insert/append 改变,如果提供了 char/const char* 参数,则由于 insert/append 上的重载,不需要临时参数。在所有情况下,都会返回一个 r 值以促进进一步的链接。不制作副本(除了要在插入位置附加/插入的参数的副本)。可能需要移动字符串的内容。

因此,像上面示例这样的链应该导致:2) -> 6) -> 11) -> 8),不会复制任何 lhs,而只是修改 r-value 的缓冲区导致从第一个操作(创建临时字符串)开始。

因此,这似乎与operator += 一样有效,一旦operator + 至少使用 r-value 参数。这是正确的吗,在 C++11 及之后的版本中使用 operator += 而不是 operator + 有什么意义,除非这两个参数都是左值字符串?

编译器还可以进行哪些优化?

编辑:澄清问题的意图。最初的部分仅是关于语言的细节(实现非承受);最后一个问题是关于额外的优化。

【问题讨论】:

  • 修正了我认为的错字。如果您的意思是some_fun(),请回滚。
  • 并不是真正的错字,但我想你的版本在 c++ 上下文中更清晰。
  • 使用g++ -save-temps 编译并查看不同优化级别的汇编程序输出是很有启发性的。使用-O3,它会为您的代码调用一次string::reserve() 和四次string::append()
  • @G.Sliepen:更简单:使用compiler explorer
  • 老实说,我更关心在增长字符串时重新分配复制的性能,而不是按已建立的值返回,因为它很可能得到了相当好的优化。

标签: c++ c++11 stl language-lawyer stdstring


【解决方案1】:

字符串是一个相当不透明的对象:它拥有一个内部字符缓冲区并按照它想要的方式管理它。将单个字符添加到字符串可能会以分配新缓冲区、初始部分的副本和添加的部分的副本结束。一切都取决于分配的缓冲区是否足够大以接受添加的部分。

引用说:

... 不制作任何副本(除了要在插入位置附加/插入的参数的副本)。 可能需要移动字符串的内容

换句话说,新分配,旧缓冲区的完整复制和释放...

当您谈到效率和优化时,您必须记住编译器不必遵循您编写程序的方式。由于 as-if 规则,它可以优化它想要的方式,前提是尊重可观察的行为。 C++ 标准说:

1.9 程序执行 [intro.execution]
...
5 执行格式良好的程序的一致实现应产生相同的可观察行为 作为具有相同程序的抽象机的相应实例的可能执行之一 和相同的输入。

一个注释甚至解释说:

一个实现可以随意忽略这个的任何要求 国际标准,只要结果符合要求,只要可以从 程序的可观察行为。

所以很可能a = a + b;a += b; 编译成完全相同的代码。

当您编写 C++ 程序时,您永远不必担心低级优化:编译器会关心它,并且通常说编译器比您更聪明。仅在确定真正的瓶颈时才采用这种方式,并注意低级优化,如果仅针对一种架构和一种配置上的一个编译器。

【讨论】:

  • 我不太关心效率,更关心语义,所以这是正确的,虽然对我来说不是很新,但错过了我的问题。我更关心的是调用了哪个版本的函数,然后关心这到底有多快。在两者之间可能发生重新分配的事实与 imo 的问题正交,因为除非您预先知道大小并为内容保留,否则您无法避免这种情况。
  • “所以很可能 a = a + b; 和 a += b; 编译成完全相同的代码。” 你真的检查过吗?我非常怀疑任何编译器实际上都能够进行这种优化(除非a 是临时的)。
  • a = a + b 在我看来至少需要一个额外的移动分配。最佳情况(假设可能)编译器意识到 a 即将被重新分配,因此不再需要它的值,将其视为右值,这导致情况 6),这导致对 r 值进行附加。然而,生成的 r 值仍然被分配给 a,我看不到任何方法可以在这里应用 rvo,因为 append 没有返回一个新对象。
猜你喜欢
  • 2011-06-12
  • 1970-01-01
  • 2010-09-22
  • 2010-09-29
  • 2020-11-30
  • 2020-02-29
  • 1970-01-01
  • 2017-03-09
相关资源
最近更新 更多