【问题标题】:C++ STL with jemalloc带有 jemalloc 的 C++ STL
【发布时间】:2012-03-20 05:51:00
【问题描述】:

如何将 C++ STL 容器与 jemalloc(或任何其他 malloc 实现)一起使用?

就如包含jemalloc/jemalloc.h那么简单吗?或者我应该为他们写一个分配器?

编辑:我正在处理的应用程序在其生命周期内分配和释放相对较小的对象。我想要替换默认分配器,因为基准测试表明应用程序不会扩展到超过 2 个核心。分析表明它正在等待内存分配,这就是导致缩放问题的原因。据我了解,jemalloc 将对此有所帮助。


我希望看到一个与平台无关的解决方案,因为该应用程序必须在 Linux 和 Windows 上运行。 (据我所知,在 Linux 下链接不同的实现很容易,但在 Windows 上却很难。)

【问题讨论】:

    标签: c++ memory-management stl malloc


    【解决方案1】:

    C++ 允许您替换 operator new。如果这个替换operator new调用je_malloc,那么std::allocator将间接调用je_malloc,反过来所有标准容器都会调用。

    这是迄今为止最简单的方法。编写自定义分配器需要编写整个类。替换 malloc 可能还不够(不能保证未替换的 operator new 调用 malloc),并且它具有 Adrian McCarthy 前面提到的风险

    【讨论】:

      【解决方案2】:

      如果您想在程序中的任何地方替换 malloc(我想要并且似乎也是唯一合乎逻辑的解决方案),那么您所要做的就是链接它。

      所以,如果您使用gcc,那么您所要做的就是:

      g++ yourprogram.cpp -ljemalloc
      

      但是,如果不可能,那么您必须通过其他功能使用jemalloc,例如je_mallocje_free,然后你必须重载 newdelete 运算符。

      如果您不使用特定于实现的功能(主要是统计信息),则无需包含任何标题。

      【讨论】:

      • 这可能已经过时了。在我的实验中,无论有没有-ljemalloc,G++ 都会生成完全相同的二进制文件。
      • @MaxB 试试-fno-builtin-malloc -fno-builtin-calloc -fno-builtin-realloc -fno-builtin-free 标志。还要仔细检查您的链接顺序并查看this manual page
      【解决方案3】:

      编写分配器将是最简单的解决方案,因为 stl 旨在具有可互换的分配器。这将是最简单的路径。

      一些玩游戏的项目尝试使用替代的malloc 实现来替换编译器配套库提供的mallocnews。这很容易出现各种问题,因为您最终依赖于编译器的特定实现细节和它通常使用的库。这条路充满危险。

      尝试全局替换 malloc 的一些危险:

      • 静态初始化程序顺序在 C++ 中的保证有限。除非您禁止可能分配内存的静态对象,否则无法保证在第一个调用者尝试使用它之前初始化分配器替换。运行时没有这个问题,因为编译器和运行时一起工作以确保在初始化任何静态之前完全初始化运行时。
      • 如果您动态链接到运行时库,则无法确保某些运行时库的代码尚未绑定到其自己的实现。在重新分发您的应用程序时,尝试修改编译器的运行时库可能会导致许可问题。
      • 所有其他分配方法可能并不总是最终依赖malloc。例如,new 的实现可能会绕过malloc 进行大分配,并直接调用操作系统来分配内存。这需要进行跟踪,以确保此类分配不会意外发送到替代free

      我相信 Chromium 和 Firefox 都已经取代了分配器,但它们玩了一些肮脏的把戏,并且可能必须随着编译器、链接器和运行时的发展而更新他们的方法。

      【讨论】:

      • 我更新了我的问题来回答你的问题。替换 new 有哪些问题?
      • 如果你只是想用通常的 C++ 恶作剧替换 new,你可以过得去。当人们试图在整个程序中替换 malloc 时,会变得非常麻烦。
      • 这正是我想做的:在整个程序中替换malloc。但我不想编写自己的实现;我只想使用另一个(经过良好测试)。
      【解决方案4】:

      让自己成为分配者。这样做:

      #include <vector>
      
      template<typename T>
      struct RemoveConst
      {
          typedef T value_type;
      };
      
      template<typename T>
      struct RemoveConst<const T>
      {
          typedef T value_type;
      };
      
      template <class T>
      class YourAlloc {
      public:
          // type definitions
          typedef RemoveConst<T>              Base;
          typedef typename Base::value_type   value_type;
          typedef value_type*                 pointer;
          typedef const value_type*           const_pointer;
          typedef value_type&                 reference;
          typedef const value_type&           const_reference;
          typedef std::size_t                 size_type;
          typedef std::ptrdiff_t              difference_type;
      
          // rebind allocator to type U
          template <class U>
          struct rebind {
              typedef YourAlloc<U> other;
          };
      
          // return address of values
          pointer address(reference value) const {
              return &value;
          }
          const_pointer address(const_reference value) const {
              return &value;
          }
      
          /* constructors and destructor
          * - nothing to do because the allocator has no state
          */
          YourAlloc() throw() {
          }
          YourAlloc(const YourAlloc&) throw() {
          }
          template <class U>
          YourAlloc(const YourAlloc<U>&) throw() {
          }
          ~YourAlloc() throw() {
          }
      
          // return maximum number of elements that can be allocated
          size_type max_size() const throw() {
              return std::numeric_limits<std::size_t>::max() / sizeof(T);
          }
      
          // allocate but don't initialize num elements of type T
          pointer allocate(size_type num, const void* = 0) {
              return (pointer)je_malloc(num * sizeof(T));
          }
      
          // initialize elements of allocated storage p with value value
          void construct(pointer p, const T& value) {
              // initialize memory with placement new
              new((void*)p)T(value);
          }
      
          // destroy elements of initialized storage p
          void destroy(pointer p) {
              // destroy objects by calling their destructor
              p->~T();
          }
      
          // deallocate storage p of deleted elements
          void deallocate(pointer p, size_type num) {
              je_free(p);
          }
      };
      
      // return that all specializations of this allocator are interchangeable
      template <class T1, class T2>
      bool operator== (const YourAlloc<T1>&,
          const YourAlloc<T2>&) throw() {
          return true;
      }
      template <class T1, class T2>
      bool operator!= (const YourAlloc<T1>&,
          const YourAlloc<T2>&) throw() {
          return false;
      }
      
      int main()
      {
          std::vector<int, YourAlloc<int>> vector;
      
          return 0;
      }
      

      代码复制自here

      【讨论】:

      • 分配器可能是个好主意。如果大部分数据都存储在分配器感知容器中,那么这是一个非常好的解决方案。
      【解决方案5】:

      可能会出现问题,因为不会调用构造函数。您可以使用operator new 的不同选项(具有比new 更多的选项),它可以在不调用构造函数的情况下分配内存,或者在已分配的内存中调用构造函数。 http://www.cplusplus.com/reference/std/new/operator%20new%5B%5D/

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-09-30
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2023-02-12
        • 1970-01-01
        相关资源
        最近更新 更多