不必要地清空移出的 std::string

2023-11-24

libstdc++ 和 libc++ 都进行了移出std::string对象为空,即使原始存储的字符串很短并且应用了短字符串优化。在我看来,这种排空使额外的和不必要的运行时开销。例如,这里是移动构造函数std::basic_string来自 libstdc++:

basic_string(basic_string&& __str) noexcept
  : _M_dataplus(_M_local_data(), std::move(__str._M_get_allocator())) {
    if (__str._M_is_local()) 
      traits_type::copy(_M_local_buf, __str._M_local_buf, _S_local_capacity + 1);
    else {
      _M_data(__str._M_data());
      _M_capacity(__str._M_allocated_capacity);
    }
    _M_length(__str.length());
    __str._M_data(__str._M_local_data());  // (1)
    __str._M_set_length(0);                // (2)
  }

(1) 是一项作业,即如果字符串很短,则无用, since data已设置为本地数据,所以我们只需为指针分配与之前分配的值相同的值。

(2)清空字符串设置字符串大小并重置本地缓冲区中的第一个字符,据我所知,标准没有要求.

通常,库实现者会尝试尽可能高效地实现标准(例如,删除的内存区域不会被清零)。我的问题是,是否可能有任何特殊原因导致移出的字符串被清空,即使它不是必需的,并且它会增加不必要的开销。这可以很容易地消除,例如,通过:

basic_string(basic_string&& __str) noexcept
  : _M_dataplus(_M_local_data(), std::move(__str._M_get_allocator())) {
    if (__str._M_is_local()) {
      traits_type::copy(_M_local_buf, __str._M_local_buf, _S_local_capacity + 1);
      _M_length(__str.length());
    }
    else {
      _M_data(__str._M_data());
      _M_capacity(__str._M_allocated_capacity);
      _M_length(__str.length());
      __str._M_data(__str._M_local_data());  // (1)
      __str._M_set_length(0);                // (2)
    }
  }

对于 libc++,字符串移动构造函数确实清空了源代码,但这并不是必需的。事实上,这个字符串实现的作者就是领导 C++11 移动语义提案的同一个人。 ;-)

libc++字符串的这种实现实际上是从向外移动成员的角度设计的!

这是省略了一些不必要的细节(例如调试模式)代码的代码:

template <class _CharT, class _Traits, class _Allocator>
basic_string<_CharT, _Traits, _Allocator>::basic_string(basic_string&& __str)
        _NOEXCEPT
    : __r_(_VSTD::move(__str.__r_))
{
    __str.__zero();
}

简而言之,此代码复制源的所有字节,然后将源的所有字节归零。需要立即注意的一件事:没有分支:此代码对长字符串和短字符串执行相同的操作。

长串模式

在“长模式”下,布局为 3 个字、一个数据指针和两个用于存储大小和容量的整数类型,减去 1 位用于长/短标志。加上分配器的空间(针对空分配器进行了优化)。

因此,这会复制指针/大小,然后清空源以释放指针的所有权。这还将源设置为“短模式”,因为短/长位意味着零状态下的短。此外,短模式中的所有零位都表示零大小、非零容量的短字符串。

短串模式

当源是短字符串时,代码是相同的:复制字节,并将源字节清零。在短模式下,没有自引用指针,因此复制字节是正确的算法。

现在确实在“短模式”下,源的 3 个字的清零可能会seem不必要,但要做到这一点,必须在长模式下检查长/短位和零字节。由于偶尔会发生分支错误预测(破坏管道),因此执行此检查和分支实际上比仅将 3 个字归零更昂贵。

这是 libc++ 的优化 x86(64 位)程序集string移动构造函数。

std::string
test(std::string& s)
{
    return std::move(s);
}

__Z4testRNSt3__112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEEE: ## @_Z4testRNSt3__112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEEE
    .cfi_startproc
## %bb.0:
    pushq   %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset %rbp, -16
    movq    %rsp, %rbp
    .cfi_def_cfa_register %rbp
    movq    16(%rsi), %rax
    movq    %rax, 16(%rdi)
    movq    (%rsi), %rax
    movq    8(%rsi), %rcx
    movq    %rcx, 8(%rdi)
    movq    %rax, (%rdi)
    movq    $0, 16(%rsi)
    movq    $0, 8(%rsi)
    movq    $0, (%rsi)
    movq    %rdi, %rax
    popq    %rbp
    retq
    .cfi_endproc

(没有分支!)

<aside>

短字符串的内部缓冲区的大小也针对移动成员进行了优化。内部缓冲区与“长模式”所需的 3 个字“联合”,以便sizeof(string)不需要比长模式更多的空间。尽管这个紧凑sizeof(3个主要实现中最小的),libc++在64位架构上拥有最大的内部缓冲区:22char.

小的sizeof转换为更快的移动成员,因为所有这些成员所做的只是对象布局的复制和零字节。

See 这个 Stackoverflow 答案有关内部缓冲区大小的更多详细信息。

</aside>

Summary

所以综上所述,在“长模式”下需要将source设置为空字符串来转移指针的所有权,并且also出于性能原因,在短模式下是必要的,以避免管道损坏。

我对 libstdc++ 实现没有评论,因为我没有编写该代码,而且您的问题无论如何已经做得很好了。 :-)

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

不必要地清空移出的 std::string 的相关文章

随机推荐

  • 创建多个可放置的兄弟姐妹,并将其放置在彼此之上

    我正在尝试创建多个彼此相邻的 jquery droppable 其中某些部分可能重叠 在这些情况下 我希望位于顶部 z 索引明智 的那个是贪婪的 我尝试过设置greedy truedroppable 中的选项 但这似乎没有帮助 我也尝试过r
  • 调试时会忽略依赖项 org.json:json:20090211,因为它可能与 Android 提供的内部版本冲突

    当我运行 android studio 时 出现以下警告 调试时会忽略依赖项 org json json 20090211 因为它可能与 Android 提供的内部版本冲突 如果出现问题 请用jarjar重新打包更改类包 我该如何解决这个错
  • 合并多个文件的 MD5 哈希值

    我有 7 个文件要为其生成 MD5 哈希值 哈希值用于确保数据存储的远程副本与本地副本相同 不幸的是 这两个数据副本之间的链接速度非常慢 数据的更改非常罕见 但我要求数据始终 或尽快 同步 我不想通过我的 非常慢的 通信链路传递 7 个不同
  • Entity Framework Oracle 和 Sql Server - 如何构建独立于数据库的应用程序

    我们正在尝试构建一个同时使用 Oracle 和 SQL Server 的数据访问层 不同时 我们使用 EF Model first 来创建模型并创建用于构建数据库的 SQL 脚本 我们的第一个想法是创建 2 个 EDMX 文件 每种类型一个
  • 为什么 Go HTTPS 客户端不重用连接?

    我有一个 http 客户端 它创建与主机的多个连接 我想设置它可以设置到特定主机的最大连接数 go的request Transport中没有这样的选项 我的代码看起来像 package main import fmt net http ne
  • 从 API 检索 Instagram 视频嵌入 URL

    我正在尝试以编程方式获取 Instagram 视频的嵌入链接 不幸的是 Instagram 的 oEmbed 端点似乎将视频视为照片 并且仅返回关键帧图像 而不提供嵌入链接 有谁知道一种方法可以检索 Instagram 视频的嵌入链接 而无
  • 如何在 PHP 中将 XML 转换为数组?

    我想将下面的 XML 转换为 PHP 数组 关于我如何做到这一点有什么建议吗
  • SQLCE 连接:保持打开还是关闭?

    考虑到移动设备上的性能 您认为 SQLCE 连接的最佳方法是什么 在应用程序运行期间保持其打开状态 或者在需要调用数据库时将其关闭 显然 这在一定程度上取决于您的应用程序的性质 但是我很想知道该小组已经实施了哪些内容以及原因 你绝对应该看史
  • Spring Boot 内存消耗增加超出 -Xmx 选项

    我注意到 Spring Boot 应用程序不遵守通过 Xmx 选项设置的内存量 例如 java Xss64m Xmx64m jar test jar 我还在控制台上打印了应用程序在启动时实际使用的内存量 并显示 最大内存 61M long
  • 如何更改全景项目标题的字体大小?

    设置全景项目标题的字体大小一次以便其可用于我的应用程序中的所有项目标题的最简单方法是什么 目前还没有一种方法可以自动为应用程序中的所有标头执行此操作 您需要为每一项设置样式 隐式样式将在 Mango 更新中出现 届时应该可以完成此操作 Up
  • 如何在 python 中处理机器学习中缺失的 NaN

    在应用机器学习算法之前如何处理数据集中的缺失值 我注意到删除缺失的 NAN 值并不是一件明智的事情 我通常使用 pandas 进行插值 计算平均值 并填充数据 这确实有效并提高了分类准确性 但可能不是最好的做法 这是一个非常重要的问题 处理
  • 如何配置 Visual Studio 将所有 TypeScript 文件合并到一个 JavaScript 文件中?

    使用 tsc 命令就像运行一样简单 tsc out all js js ts 当我构建项目时 如何配置 Visual Studio 来执行此操作 我发现了一个可能更简单的解决方案 只需修改您正在构建的项目 csproj vbproj 的构建
  • 为什么编译器不执行类型转换?

    考虑以下代码 include
  • Heroku + socket.io 广播上的多个测功机

    我似乎遇到一个问题 当我有超过 1 个 dyno 时 Heroku 上的 node js 应用程序中的 socket io 广播似乎不起作用 当我将其缩放到 1 时 它就完美地工作了 关于这件事有什么我需要知道的吗 也许有不同的方式向所有测
  • Android:支持所有设备的背景图像大小(以像素为单位)

    我正在创建一个将在所有 Android 设备上运行的应用程序 我想为我的应用程序创建 xhdpi 图形 我的应用程序是全屏的 我对创建图形感到困惑 谁能告诉我背景图像的最佳尺寸 以像素为单位 例如 xhdpi 720x1280 像素 高清
  • tf.initialize_all_variables() 和 tf.initialize_local_variables() 有什么区别?

    我正在查看此示例中的代码 完全连接的读者 py 我对第 147 行和第 148 行感到困惑 init op tf group tf initialize all variables tf initialize local variables
  • 如何进行 DOM 的中序遍历? [关闭]

    Closed 这个问题不符合堆栈溢出指南 目前不接受答案 我发现了这个可笑的技术文档 http www w3 org TR DOM Level 2 Traversal Range traversal html Traversal Docum
  • 如何一致地逐行合并两个文件

    我有两个文件 文件1 txt 文件2 txt 这些文件只是示例 如何合并这两个文件 以创建文件 合并文件 txt如示例3 我现在正在写一个康壳公司 ksh 脚本 因此可以使用 KornShell 完成合并 AWK sed a Perl单线等
  • 单表继承还是类表继承?

    我正在阅读有关类表继承 CTI 的内容 发现我总体上更喜欢它 我的问题是 单表继承 STI 是否有任何特定的用例 您可以在 CTI 上使用它 I read http rhnh net 2010 07 02 3 reasons why you
  • 不必要地清空移出的 std::string

    libstdc 和 libc 都进行了移出std string对象为空 即使原始存储的字符串很短并且应用了短字符串优化 在我看来 这种排空使额外的和不必要的运行时开销 例如 这里是移动构造函数std basic string来自 libst