为std::容器实现的日志分配器?

5

X: 我需要知道程序的每个部分使用了多少内存。我的程序大量使用C++标准库。特别是,我想知道每个对象使用了多少内存。

我如何做到这一点:要记录some_vector的消耗,只需编写:

my::vector<double,MPLLIBS_STRING("some_vector")> some_vector;

where

namespace my {
  template<class T, class S>
  using vector = std::vector<T,LoggingAllocator<T,S>>;
}

登录分配器的实现如下:
template<class T, class S = MPLLIBS_STRING("unknown")> struct LoggingAllocator {
  // ... boilerplate ...

  pointer allocate (size_type n, std::allocator<void>::const_pointer hint = 0) {
    log_allocation(boost::mpl::c_str<S>::value);
    // allocate_memory (I need to handle it myself)
  }
  void destroy (pointer p) ; // logs destruction
  void deallocate (pointer p, size_type num); // logs deallocation
};

问题:有没有更好的通用方法来实现这种行为?我所说的更好是指,更简单、更好、不依赖于boost::mplmpllibs::metaparse等库。理想情况下,我只想写下面的代码:

my::vector<double,"some_vector"> some_vector;

并且完成它。

这对我来说已经相当通用了,你还想要更通用的吗? - PlasmaHH
1
虽然这不是回答问题的方法,但如果只有开发人员需要了解内存使用情况,最好使用内存分析工具而不是对整个代码库进行仪器化。 - daramarak
@daramarak:这真的取决于你想要什么以及你可以使用哪些工具;例如,尽管我很喜欢Massif,但它并不像简单的记录器那样适用于生产环境。 - Matthieu M.
@PlasmaHH 我认为这也是通用的,但整个metaparse和boost::mpl依赖关系是需要考虑的。我希望有一种更简单的方法来做到这一点,不需要依赖于metaparse/boost::mpl。我将在问题中更新此内容。 - gnzlbg
@daramarak 用户也需要。这是一个HPC应用程序,我们的用户也是开发人员。但是很好的提示!谢谢!还要考虑ThreadSpotter、Scalasca等工具。 - gnzlbg
1个回答

6

如果您不想自己处理所有的分配,虽然可能不是“更通用”,但您可以继承标准分配器std::allocator

template<class T, class S = MPLLIBS_STRING("unknown"), class Allocator = std::allocator<T>>
struct LoggingAllocator : public Allocator {
    // ...
};

allocate/destroy/deallocate函数中记录日志,然后调用父类的方法:
pointer allocate (size_type n, std::allocator<void>::const_pointer hint = 0) {
    log_allocation(boost::mpl::c_str<S>::value);
    return Allocator::allocate(n, hint);
}

请注意,std::allocator并不是为继承而设计的,这也正是它没有虚析构函数的原因。


很好!这并不适用于我的情况,因为我需要自己处理内存分配,但这启发了我抽象日志记录策略的想法:D 尽管我一直在尝试让它更简单 :D - gnzlbg
6
如果你继承另一个分配器,那么很关键的一点是要创建一个新版本的“rebind”函数,以便如果重新绑定了分配器的类型,则返回你自己的分配器,而不是你继承来的那个分配器。 - Dave S
由于分配器是按值复制的,缺少虚拟析构函数并不是问题。你可能会被“切割”(特别是如果基础分配器定义了“rebind”,而派生分配器没有定义),但正确的析构函数将运行。 - Jonathan Wakely
1
@DaveS, +1,尽管请注意在C++11中定义rebind是可选的,因此只有当基类这样做时,派生类才需要这样做。如果基类不提供rebind,那么std::allocator_traits<A>::rebind_alloc会做正确的事情。但是,既然std::allocator确实定义了rebind,那么你绝对是正确的,在这种情况下需要它。 - Jonathan Wakely

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接