调试时 GCC 中的自定义 C++ 分配器太慢。有解决办法吗?

2024-01-25

我正在努力解决自定义分配器的性能问题。 我的问题是关于调试版本。

通常情况下,如果只有一点点下降,我并不介意。但目前我正在以 4fps 播放某些内容,而如果没有自定义分配器,则播放速度为 60fps(并且可能会更快)。这使得软件开发变得更加困难。

我一直把它确定下来......基本上继承了标准分配器

请查看“quick-bench.com”的以下结果https://quick-bench.com/q/ep3uyYNK6rh_6f8AGAP0zIAflAA https://quick-bench.com/q/ep3uyYNK6rh_6f8AGAP0zIAflAA

here is a picture: enter image description here

蓝色条很简单:

int main() {
    std::vector<uint8_t, std::vector<uint8_t>::allocator_type> buffer;
    buffer.reserve(numBytes);
    buffer.resize(numBytes);
    return 0;
}

黄色条:

template<typename T>
class CustomAllocatorType : public std::vector<uint8_t>::allocator_type {};

int main() {
    std::vector<uint8_t, CustomAllocatorType<uint8_t>> buffer;
    buffer.reserve(numBytes);
    buffer.resize(numBytes);
    return 0;
}

用以下内容封装自定义分配器:

#pragma GCC push_options
#pragma GCC optimize ("-O3")
// ....
#pragma GCC pop_options

没有任何效果。我想我需要对向量实例本身执行此操作,但我不想走那么远......

有谁知道这个问题的解决方案?


业绩下降原因

如果分配器是,gcc 的 libstdc++ 使用某些性能改进std::allocator. Your CustomAllocatorType是一个不同的类型std::allocator,意味着优化被禁用。请注意,我是not谈论编译器优化,而不是 gcc 对 C++ 标准库的实现专门针对std::allocator。 要命名与示例代码相关的示例,std::vector::resize() 内部调用__uninitialized_default_n_a() https://github.com/gcc-mirror/gcc/blob/95874f95095f401405d3386e2e6695351b3f97b5/libstdc%2B%2B-v3/include/bits/vector.tcc#L642其中有一个特殊的过载std::allocator https://github.com/gcc-mirror/gcc/blob/16e2427f50c208dfe07d07f18009969502c25dc8/libstdc%2B%2B-v3/include/bits/stl_uninitialized.h#L703。特殊的重载完全绕过分配器。如果你使用CustomAllocatorType, the 通用版本 https://github.com/gcc-mirror/gcc/blob/16e2427f50c208dfe07d07f18009969502c25dc8/libstdc%2B%2B-v3/include/bits/stl_uninitialized.h#L679使用它为每个元素调用分配器。这需要花费很多时间。另一个具有特殊定义且与您的简单代码示例相关的函数是_Destroy() https://github.com/gcc-mirror/gcc/blob/66d1e440e14377a373d0e3d67f478cca4fd14dea/libstdc%2B%2B-v3/include/bits/alloc_traits.h#L847.

换句话说,gcc 对 C++ 标准库的实现采取了一些措施,以确保在已知安全的情况下生成最佳代码。无论编译器优化如何,这都有效。 如果采用非优化的代码路径并且您启用了编译器优化(例如-O3),编译器通常能够识别非优化代码中的模式(例如初始化连续的琐碎元素),并且可以优化所有内容,以便最终得到相同的指令(或多或少)。

C++20 与 C++17 以及为什么你的CustomAllocatorType被打破

正如评论中指出的,使用时性能会下降CustomAllocatorType仅出现在 C++20 中,而不出现在 C++17 中。 要理解原因,请注意 gcc 的std::vector实施确实not使用Allocator从声明中std::vector<T,Allocator>作为分配器,即在您的情况下CustomAllocatorType。相反,它使用std::allocator_traits<T>::rebind_alloc<T> (see here https://github.com/gcc-mirror/gcc/blob/16e2427f50c208dfe07d07f18009969502c25dc8/libstdc%2B%2B-v3/include/bits/stl_vector.h#L87 and here https://github.com/gcc-mirror/gcc/blob/16e2427f50c208dfe07d07f18009969502c25dc8/libstdc%2B%2B-v3/include/bits/stl_vector.h#L129)。另请参阅例如这篇关于重新绑定的文章 https://stackoverflow.com/a/15225165/3740047了解更多信息。

由于您没有定义专业std::allocator_traits<CustomAllocatorType>,它使用通用的。标准says https://en.cppreference.com/w/cpp/memory/allocator_traits:

重新绑定_分配Alloc::rebind<T>::other如果存在,否则Alloc<T, Args>如果这个 Alloc 是Alloc<U, Args>

IE。如果可能的话,通用分配器会尝试委托给您的分配器。现在,你的分配器CustomAllocatorType继承自std::allocator。 C++17 和 C++20 之间的重要区别如下:std::allocator::rebind was removed https://en.cppreference.com/w/cpp/memory/allocator在 C++20 中。因此:

  • C++17: CustomAllocatorType::rebind是继承的并因此定义的并且是std::allocator。所以,std::allocator_traits<CustomAllocatorType>::rebind_alloc, 意思是std::vector最终实际使用std::allocator代替CustomAllocatorType。如果你传入一个CustomAllocatorType实例中的std::vector构造函数,你最终会得到拼接。
  • C++20: CustomAllocatorType::rebind is not定义的。因此,std::allocator_traits<CustomAllocatorType>::rebind_alloc is CustomAllocatorType and std::vector最终使用CustomAllocatorType.

所以C++17版本使用std::allocator因此享受上述基于库的优化,而 C++20 版本则不然。

您的代码根本不正确,或者至少是 C++17 版本不正确。std::vector在 C++17 中根本不使用你的分配器。您还可以注意到,如果您尝试致电buffer.get_allocator()在您的示例中,它将无法在 C++17 中编译,因为它将尝试转换std::allocator(内部使用)CustomAllocatorType.

我认为解决这个问题的正确方法是定义CustomAllocatorType::rebind而不是专门化std::allocator_traits (see here https://stackoverflow.com/a/54092439/3740047 and here https://stackoverflow.com/a/71875925/3740047),像这样:

template<typename T>
class CustomAllocatorType: public std::allocator<T> 
{
  template< class U > struct rebind {
    typedef CustomAllocatorType<U> other;
  };
};

当然,这样做意味着C++17版本在调试时会很慢,但实际上可以正常工作。

我认为这也再次表明了一般规则:从 C++ 标准库类型继承通常是一个坏主意。如果CustomAllocatorType没有继承自std::allocator,问题一开始就不会出现(而且,因为您需要考虑如何正确设置元素)。

提高绩效

假设分配器已针对 C++17 进行修复,或者您使用 C++20,则调试时的性能会很差,因为库实现使用上述函数的通用版本来填充和销毁数据。不幸的是,这一切都是一个实施细节图书馆的,这意味着有no强制生成良好代码的良好标准方法。

黑客解决方案

在您的简单示例中有效(并且可能仅在那里!)的一个技巧是定义相关函数的自定义重载,例如:

#include <bits/stl_uninitialized.h>
#include <cstdint>
#include <cstdlib>

// Must be defined BEFORE including <vector>!
namespace std{
  template<typename _ForwardIterator, typename _Size, typename _Tp>
  inline _ForwardIterator
  __uninitialized_default_n_a(_ForwardIterator __first, _Size __n, CustomAllocatorType<_Tp>&)
  { return std::__uninitialized_default_n(__first, __n); }


  template<typename _ForwardIterator, typename _Tp>
  _GLIBCXX20_CONSTEXPR inline void
  _Destroy(_ForwardIterator __first, _ForwardIterator __last, CustomAllocatorType<_Tp>&) {
    _Destroy(__first, __last);
  }
}

这些是从 gcc 复制粘贴的std::allocator重载(here https://github.com/gcc-mirror/gcc/blob/16e2427f50c208dfe07d07f18009969502c25dc8/libstdc%2B%2B-v3/include/bits/stl_uninitialized.h#L701 and here https://github.com/gcc-mirror/gcc/blob/66d1e440e14377a373d0e3d67f478cca4fd14dea/libstdc%2B%2B-v3/include/bits/alloc_traits.h#L847),但超载CustomAllocatorType。实际应用中需要更特殊的重载(例如,is_copy_constructible and is_move_constructible https://github.com/gcc-mirror/gcc/blob/66d1e440e14377a373d0e3d67f478cca4fd14dea/libstdc%2B%2B-v3/include/bits/alloc_traits.h#L776 or __relocate_a_1 https://github.com/gcc-mirror/gcc/blob/16e2427f50c208dfe07d07f18009969502c25dc8/libstdc%2B%2B-v3/include/bits/stl_uninitialized.h#L1005,不知道还有多少)。定义上面两个函数before的包括<vector>为您的最小示例带来良好的调试性能。至少它对我本地使用 gcc 11.2 是这样做的。它不起作用快速板凳 https://quick-bench.com/q/4DQhSeMGIKyGcrMUaKFCUOmCF_8因为快板凳强制包含benchmark/benchmark.h https://github.com/FredTingaud/quick-bench-back-end/blob/7430344cc8457767a6952ed5d89acb4e0c73fd37/src/libquick.js#L111在你的任何代码之前,然后依次包括<vector> https://github.com/google/benchmark/blob/dc901ff9090e2b931433790cc44afc3af3b09ab2/include/benchmark/benchmark.h#L188(还要比较接下来的第二个要点)。

这个黑客在多个层面上都很糟糕:

  • 绝对是非标准。它仅适用于 stdlibc++,并且可能会在库版本的任何升级或降级时中断。
  • 您还需要确保定义了重载before the <vector>包含标头,否则它们将不会被拾取。原因是,打电话给std::__uninitialized_default_n_a() https://github.com/gcc-mirror/gcc/blob/95874f95095f401405d3386e2e6695351b3f97b5/libstdc%2B%2B-v3/include/bits/vector.tcc#L642是合格的,即是std::__uninitialized_default_n_a(arguments)而不是__uninitialized_default_n_a(arguments),意味着在定义之后重载std::vector未找到(参见例如这个帖子 https://stackoverflow.com/q/18598862/3740047 or this one https://stackoverflow.com/a/32604022/3740047)。正如上面已经解释的,这就是黑客在快速板凳上失败的原因。另外,如果你在某些地方搞砸了,你可能会违反单一定义规则(这可能会导致更多奇怪的情况)。
  • 该示例 hack 假设分配和释放内存不需要使用CustomAllocatorType, 就像std::allocator。我非常怀疑这是否适合你的真实情况CustomAllocatorType执行。但也许你实际上可以实现例如__uninitialized_default_n_a()正确且更有效地为您CustomAllocatorType通过在分配器上调用适当的函数。

我不建议这样做。但根据用例,这可能是一个可行的解决方案。

启用-Og

编译时,我确实使用 gcc 获得了明显更好的性能一切 with -Og。它尝试执行一些优化,但不会过多干扰调试体验。在你的小例子中,性能得到了提高慢 160 倍 https://quick-bench.com/q/ep3uyYNK6rh_6f8AGAP0zIAflAA to 慢 5 倍 https://quick-bench.com/q/6Z2BAjQYWJrCdyNwm5o-W7-WQi8相比于std::allocator版本。因此,如果您无法更改编译器,我认为这可能是最好的方法。

使用铿锵声

切换到 clang (没有任何优化标志)似乎可以在一定程度上提高性能。对于 libstdc++,自定义分配器版本是“唯一”的慢 90 倍 https://quick-bench.com/q/8mDtjbosVRD_3uhPVdzkOdtjY5E。 令人惊讶的是,使用 libc++快速板凳 https://quick-bench.com/q/Fjvu8ZNlWeDf60YNa3ekj-B39gY报告了大致相同的性能。不幸的是,我无法在本地重现这个:libc++ 也需要很长时间。不知道为什么本地和快速板凳上的结果不同。

但我可以重现 clang 正在优化的情况-Og比 gcc 好得多,并且与自定义分配器提供大致相同的性能。这同时适用于libstdc++ 库 https://quick-bench.com/q/hE6PkfPKuRMtHC5boBLJvOg9QG8 and libc++ https://quick-bench.com/q/aCJY_NXV_Zw8he-cgVPcQ45XbnI.

所以我的建议是使用 clang,可能与 libc++ 一起使用,并使用-Og.

另类想法

本地启用优化(#pragma GCC optimize ("-O3")等)是相当不可靠的。它对我不起作用。最可能的原因是优化标志没有传播到实例化std::vector因为它的定义完全在其他地方。您可能需要通过优化来编译 C++ 标准库头本身。

另一个想法是使用不同的容器库。例如,boost https://www.boost.org/doc/libs/1_79_0/doc/html/boost_container_header_reference.html#header.boost.container.vector_hpp has a vector班级。但我还没有检查它的调试性能是否会更好。

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

调试时 GCC 中的自定义 C++ 分配器太慢。有解决办法吗? 的相关文章

随机推荐

  • iFrame:如何使用 javascript 将服务器响应(HTML)直接显示到 iFrame 中?

    我收到一个简单的服务器响应 它是一个 html 文件 我想在 iFrame 中显示相同的内容 而不将该文件保存到我的工作区或计算机中 我正在进行 ajax 调用 如下所示 Ext Ajax request url url method PO
  • 字符识别(OCR算法)[关闭]

    Closed 这个问题需要多问focused help closed questions 目前不接受答案 我正在开发一个项目 其中我必须开发 OCR 算法 我必须从图像中读取文本 然后将其转换为不同的语言 所以我的第一个任务是从图像中获取文
  • Android编程打开DataUsage设置页面

    在 Android 5 0 中 移动数据设置可在数据使用设置中使用 我想在android中打开数据使用设置页面 但我没有找到任何打开它的意图 为什么要打开数据使用设置页面 你尝试过这种方法吗 final Intent intent new
  • MIN 和 MAX 宏的 Swift 等效项

    在 C Objective C 中 可以使用 MIN 和 MAX 宏找到两个数字之间的最小值和最大值 Swift 不支持宏 并且语言 基础库中似乎没有等效的宏 是否应该采用自定义解决方案 也许基于这样的泛型one http www cplu
  • 未捕获的语法错误:意外的标记 e

    I am getting Uncaught Syntax Error newly the only addition is sending retrieve json data from server to client How to in
  • 通过 Bash Shell 脚本从 url 列表中提取父域名

    我有一个像这样的网址列表 http noto zrobimystrone pl pucenter images NGdocs http visionwebmkt com unsubscribe php M 879552 C b744d324
  • 列出给定库模块中的谓词

    有没有办法列出 SICStus Prolog 给定库模块中定义的所有谓词 例如如果我加载列表模块 use module library lists 我可以从提示符中运行另一个谓词来告诉我刚刚导入了哪些谓词吗 这适用于 SWI Prolog
  • 哪种 pyspark 抽象适合我的大型矩阵乘法?

    我想执行大型矩阵乘法 C A B T然后通过应用严格的阈值来过滤 C 收集形式为 行索引 列索引 值 的列表 A 和 B 很稀疏 条目大多为零 它们最初表示为稀疏 scipy csr 矩阵 矩阵的大小 当它们是密集格式时 答 9G 900
  • 有关构建 RDBMS 的资源 [关闭]

    Closed 这个问题正在寻求书籍 工具 软件库等的推荐 不满足堆栈溢出指南 help closed questions 目前不接受答案 我正在考虑实施 RDBMS 有没有关于数据库内部工作原理以及在开始构建自己的数据库时需要了解哪些内容的
  • JavaScript 选择/范围框架

    我一直在使用选择 范围对象 并且由于浏览器之间对于特定选择 范围内容 甚至超过 DOM 存在大量不一致 我想知道是否有一个框架可以帮助我解决这些问题 我编写了一个新的范围 选择库 名为Rangy http code google com p
  • 将字符串转换为带时区的日期

    我有一个格式为 yyyy MM dd hh mm a 的字符串 我可以单独获取时区对象 其中上面的字符串代表日期 我想将其转换为以下格式 yyyy MM dd HH mm ss Z 我怎样才能做到这一点 您可以使用简单日期格式 http d
  • AVAudioSession:某些蓝牙设备在我的应用程序上无法正常工作

    我正在使用 AVAudioSession 开发一个快速的音频 视频和文本聊天 iOS 应用程序 每当我选择使用某些蓝牙设备时 设备上播放的声音不是应用程序音频流 每当发送 接收消息时 它们仅播放文本聊天库发送的系统声音 并非所有蓝牙设备都会
  • null 参数的 IllegalArgumentException 或 NullPointerException? [关闭]

    Closed 这个问题是基于意见的 help closed questions 目前不接受答案 我有一个简单的属性设置方法null不适合此特定属性 我一直在这种情况下左右为难 我应该扔一个吗 IllegalArgumentException
  • 如何从 Oreo 的内部存储中打开 PDF 文件?

    我尝试在 Oreo 中打开 PDF 文件 但打不开 我没有收到任何错误 有什么问题吗 PDF 文件无法打开 仅显示黑屏 在 logcat 中没有显示错误 怎么了 我该如何解决这个问题 我引用了很多链接但没有得到解决方案 我也尝试了很多代码但
  • 对于简单的类型不匹配,出现错误“未为 `std::string::String` 实现特征 `std::ops::FnMut<(char,)>`”[重复]

    这个问题在这里已经有答案了 let mystring format the quick brown fox assert mystring ends with mystring Error the trait std ops FnMut l
  • 删除“NUL”字符

    我的记事本 中有这样的字符 当我尝试复制整行时 我实际上正在复制所有内容 直到 NUL File 1 我想做的就是替换那些空的 什么都没有 这样我就可以复制我的整行 也许有任何关键字可以告诉记事本 或任何其他可能有帮助的程序 替换这些字符
  • 如何减少 CUDA 同步延迟/延迟

    这个问题与使用cuda流运行多个内核有关 CUDA中有很多同步命令 cudaStream同步 Cuda设备同步 cuda线程同步 还有 cudaStreamQuery 来检查流是否为空 我注意到在使用探查器时 这些同步命令会给程序带来很大的
  • Android 中的聊天应用程序,以便发送者和接收者消息应该位于不同的一侧

    protected void onPostExecute ArrayList
  • 如何使用资产管道在邮件程序中拥有样式表的绝对路径?

    我的邮件模板中的视图助手为我提供了样式表和图像的相对 URL 当然 例如 如果我在 Gmail 中查看电子邮件 则此方法将不起作用 In apps views layouts mailer html erb 呈现为 a href http
  • 调试时 GCC 中的自定义 C++ 分配器太慢。有解决办法吗?

    我正在努力解决自定义分配器的性能问题 我的问题是关于调试版本 通常情况下 如果只有一点点下降 我并不介意 但目前我正在以 4fps 播放某些内容 而如果没有自定义分配器 则播放速度为 60fps 并且可能会更快 这使得软件开发变得更加困难