前言

我们项目组前段时间排查和分析压测环境下的某些业务模块大量索引结构的内存问题。通用的工具比如 jemalloc+jeperf 或者 tcmalloc+gperf 的组合过于底层,一方面开启跟踪开销较高,另一方面也是会产生过多噪音数据影响判断。所以我针对我们的智能指针(包含 std::shared_ptr 和我最近写了个非线程安全的版本的 strong_rc_ptr , 这个后面有空再分享)和STL容器实现了allocator来帮助动态的手动插桩来分析问题。 最终的效果是可以通过一键替换类型申明的Allocator来插入动态控制和插桩统计的能力,这里分享一下手夯标准STL allocator的一些实现细节,方便其他小伙伴如果需要做类似的实现来参考。

基础接口

我们可以参考 std::allocator 首先是Allocator有一些基础接口,用于控制内存分配、释放、构造、析构还有max_size等。std::allocator<T>::allocate, std::allocator<T>::deallocatestd::allocator<T>::constructstd::allocator<T>::destroy , std::allocator<T>::max_size 等。这些比较简单就不做过多赘述。仅仅有两个需要稍微关注下的点。

  • 首先是C++20以后大多数接口开始转入 constexpr , 如果需要完整支持就业需要给自己的实现也加上 constexpr
  • 其次是 std::allocator<T>::constructstd::allocator<T>::destroy 这类构造和析构函数在C++20之后被移除了,转而使用 std::allocator_traits<Alloc>::constructstd::allocator_traits<Alloc>::destroy 。我们要实现定制化构造和析构的话需要优先实现 allocator_traits 特化。

Allocator rebind

我们可以看到 allocator_traits 的标准里有:

TypeDefinition
rebind_alloc<T>Alloc::rebind<T>::other if present, otherwise SomeAllocator<T, Args> if this Alloc is of the form SomeAllocator<U, Args>, where Args is zero or more type arguments
rebind_traits<T>std::allocator_traits<rebind_alloc<T>>

allocator 的标准里也有:

TypeDefinition
rebindtemplate< class U > struct rebind { typedef allocator<U> other; };

那这个有什么用呢?

比如我们声明一个 std::unordered_map<K, V> 的时候,实际指向的是 std::unordered_map<K, V, std::hash<K>, std::euqal_to<V>, std::allocator<std::pair<const K, V>>> 。 这里可以看到allocator是 std::allocator<std::pair<const K, V>> 。但是实际上unordered_map内部还要维护Hash桶,还要维护Node的树形结构。这些也是需要分配内存的,那这些怎么声明allocator类型呢? 就是通过 rebind_alloc<T>/rebind<T>::other 来实现。

实际上,对于标准C++容器而言,假设我们有自己的allocator类:

template<class T, class... Args>
custom_allocator;

我们声明容易使用的类型是 custom_allocator<T> ,当需要 U 类型的allocator时,会自动把allocator类型rebind到 custom_allocator<U> 。那为什么还需要通过 rebind_alloc<T>/rebind<T>::other 来实现而不是定死这个规则呢?这里的问题就出在 custom_allocator<T, Arg...> -> custom_allocator<U, Arg...> 的这个后面的类型参数 Args... 上。有时候并不能满足我们的需求。

比如在我们自己实现的allocator中:

template <class T, class BackendAllocator = ::std::allocator<T>>
struct UTIL_SYMBOL_VISIBLE allocator {
  using background_allocator_type = BackendAllocator;
  // ...
};

它是在 BackendAllocator 的基础上增加了统计分析能力,实际分配内存和构造析构还是使用 BackendAllocator 。 当STL默认的实现里传入 allocator<T, std::allocator<T>> 时,如果rebind到 U 会变成, allocator<U, std::allocator<T>> 。 这显然不是我们想要的,我们想要的是 allocator<U, std::allocator<U>> 。所以我们就会特化自己的实现:

// STL wiil rebind rebind_alloc to allocator<U, BackendAllocator>, in which BackendAllocator may not be right
// So we always use rebind<U>::other to support allocator rebinding
template <class U>
struct rebind {
  using __rebind_backend_type_other =
      typename ::std::allocator_traits<background_allocator_type>::template rebind_alloc<U>;
  using other = allocator<U, __rebind_backend_type_other>;
};

检测类型和设置Alias的两种方式

回顾前面标准的实现要求,可以看到 allocator_traits 内某些类型定义需要根据Allocator是否有某些定义来选择。

比如上面对 std::allocator_traits<Alloc>::rebind_alloc<T> 的定义。这种编译期检测成员是否存在来走不同分支的实现,C++里要借助一些元编程的技巧。 顺着整个C++标准和编译器的演进,大体上有以下这几种方式。

实现一: 通过类型推导

template <class AllocType>
struct allocator_traits {
// Foreach 每个类型
private:
  template <class __TCNT>
  static typename __TCNT::propagate_on_container_copy_assignment __nested_propagate_on_container_copy_assignment_helper(__TCNT*);
  static ::std::false_type __nested_propagate_on_container_copy_assignment_helper(...);

public:
  using propagate_on_container_copy_assignment =
      decltype(__nested_propagate_on_container_copy_assignment_helper(static_cast<propagate_on_container_copy_assignment*>(nullptr)));

};

实现二: 通过模板参数模板推断(C++17)+Concepts(C++ 20)requires关键字

一开始我们采用下面这个方法实现,让检测类型的模板变成公共模板。

template <class AllocType>
struct allocator_traits {
// Using void_t magic in C++17
#if ((defined(__cplusplus) && __cplusplus >= 201703L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L)) ||
    (defined(__cpp_template_template_args) && __cpp_template_template_args)
#  if __cpp_concepts
  // Implementation of the detection idiom (negative case).
  template <class DefaultType, template <class...> class DetectTemplateType, class... TemplateArgs>
  struct __nested_type_detected_or {
    using type = DefaultType;
    using __is_detected = ::std::false_type;
  };

  // Implementation of the detection idiom (positive case).
  // 注意这里的两个 `concepts` 分支里的两个 `requires`, 后一个是声明 Concept ,前一个是声明要满足这个 Concept 。
  template <class DefaultType, template <class...> class DetectTemplateType, class... TemplateArgs>
    requires requires { typename DetectTemplateType<TemplateArgs...>; }
  struct __nested_type_detected_or<DefaultType, DetectTemplateType, TemplateArgs...> {
    using type = DetectTemplateType<TemplateArgs...>;
    using __is_detected = ::std::true_type;
  };
#  else
  /// Implementation of the detection idiom (negative case).
  template <class DefaultType, class _AlwaysVoid, template <class...> class DetectTemplateType, class... TemplateArgs>
  struct __nested_type_detector {
    using type = DefaultType;
    using __is_detected = ::std::false_type;
  };

  /// Implementation of the detection idiom (positive case).
  template <class DefaultType, template <class...> class DetectTemplateType, class... TemplateArgs>
  struct __nested_type_detector<DefaultType, ::std::void_t<DetectTemplateType<TemplateArgs...>>, DetectTemplateType,
                                TemplateArgs...> {
    using type = DetectTemplateType<TemplateArgs...>;
    using __is_detected = ::std::true_type;
  };

  template <class DefaultType, template <class...> class DetectTemplateType, class... TemplateArgs>
  using __nested_type_detected_or = __nested_type_detector<DefaultType, void, DetectTemplateType, TemplateArgs...>;
#  endif  // __cpp_concepts

  template <class DefaultType, template <class...> class DetectTemplateType, class... TemplateArgs>
  using __nested_type_detected_or_t =
      typename __nested_type_detected_or<DefaultType, DetectTemplateType, TemplateArgs...>::type;
#endif

// Foreach 每个类型
private:
  template<class __TCNT>
  using __nested_type_propagate_on_container_copy_assignment = typename __TCNT::propagate_on_container_copy_assignment;

public:
  using propagate_on_container_copy_assignment = __nested_type_detected_or_t<::std::false_type, __nested_type_propagate_on_container_copy_assignment, AllocType>;
};

遗憾的是这个 is_detected/detected_or 还处于草案,至少当前的Clang版本(18)不兼容。

可参考:

  • GCC: <type_traits> / <experimental/type_traits>
  • Clang: <experimental/type_traits>

所以不得不还是使用老方法。这里也可以适配一下Concept。

template <class AllocType>
struct allocator_traits {
#if defined(__cpp_concepts) && __cpp_concepts
private:
  template <class DefaultType, class TemplateType>
  struct __nested_type_propagate_on_container_copy_assignment {
    using type = DefaultType;
    using value_t = ::std::false_type;
  };
  template <class DefaultType, class TemplateType>
    requires requires { typename TemplateType::propagate_on_container_copy_assignment; }
  struct __nested_type_propagate_on_container_copy_assignment<DefaultType, TemplateType> {
    using type = typename TemplateType::propagate_on_container_copy_assignment;
    using value_t = ::std::true_type;
  };

public:
  using propagate_on_container_copy_assignment =
      typename __nested_type_propagate_on_container_copy_assignment<::std::false_type, AllocType>::type
#else
private:
  template <class DefaultType, class TemplateType, class = void>
  struct __nested_type_propagate_on_container_copy_assignment {
    using type = DefaultType;
    using value_t = ::std::false_type;
  };
  template <class DefaultType, class TemplateType>
  struct __nested_type_propagate_on_container_copy_assignment<
      DefaultType, TemplateType, ::std::void_t<typename TemplateType::propagate_on_container_copy_assignment>> {
    using type = typename TemplateType::propagate_on_container_copy_assignment;
    using value_t = ::std::true_type;
  };
public:
  using propagate_on_container_copy_assignment =
      typename __nested_type_propagate_on_container_copy_assignmentE<::std::false_type, AllocType>::type
#endif
};

检测函数存在的两种实现方法

除了类型以外, allocator_traits 内某些接口实现也会根据Allocator是否有某些函数存在二选择不同的实现。

比如标准对 std::allocator_traits<Alloc>::max_size() 的行为定义:

If possible, obtains the maximum theoretically possible allocation size from the allocator a, by calling a.max_size().

If the above is not possible (e.g. Alloc does not have the member function max_size()), then returns std::numeric_limits<size_type>::max() / sizeof(value_type)

大体上有两种实现方式,本质上都是利用模板优先匹配的规则来实现。

实现一: 参数类型推导+helper类

// construct
template <typename U, typename... _Args>
struct __construct_helper {
  template <typename AllocOther,
            typename = decltype(std::declval<AllocOther*>()->construct(std::declval<U*>(), std::declval<_Args>()...))>
  static true_type __test(int);

  template <typename>
  static false_type __test(...);

  using type = decltype(__test<allocator_type>(0));
  static constexpr const bool value = type::value;
};

template <typename U, typename... _Args>
static inline constexpr
    typename ::std::enable_if<__construct_helper<U, _Args...>::value, void>::type
    _S_construct(allocator_type& __a, U* __p, _Args&&... __args) {
  __a.construct(__p, std::forward<_Args>(__args)...);
}

template <typename U, typename... _Args>
static inline constexpr
    typename ::std::enable_if<!__construct_helper<U, _Args...>::value && ::std::is_constructible<U, _Args...>::value,
                              void>::type
    _S_construct(allocator_type&, U* __p, _Args&&... __args) {
#if defined(PROJECT_TRADE_DEBUG_OBJECT_POOL_TRACE) && PROJECT_TRADE_DEBUG_OBJECT_POOL_TRACE
  global_object_pool::increase_constructor_counter_template<value_type>(reinterpret_cast<void*>(__p));
#endif

#if ((defined(__cplusplus) && __cplusplus >= 202002L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 202002L))
  ::std::construct_at(__p, std::forward<_Args>(__args)...);
#else
  ::new ((void*)__p) U(std::forward<_Args>(__args)...);
#endif
}

template <class T, class... Args>
static inline constexpr void construct(allocator_type& a, T* p, Args&&... args) noexcept(
    noexcept(_S_construct(a, p, std::forward<Args>(args)...))) {
  _S_construct(a, p, std::forward<Args>(args)...);
}

实现二: 返回值推导

// max_size
template <typename AllocOther>
static inline constexpr auto _S_max_size(const AllocOther& __a, int) -> decltype(__a.max_size()) {
  return __a.max_size();
}

template <typename AllocOther>
static inline constexpr size_type _S_max_size(const AllocOther&, ...) {
  return ::std::numeric_limits<size_type>::max();
}

static inline constexpr size_type max_size(const allocator_type& a) noexcept {
  return _S_max_size(a, 0);
}

通用标准化 allocator_traits 实现的辅助类

可以看到我们想实现一个完整的 allocator 的必须接口还好并不是很多,但是 allocator_traits 的内容还是蛮多的。特别是很多实现需要借助一些C++的detection idiom技巧,还要考虑跨C++标准的兼容性,比较复杂。 所以为了降低这个实现难度,我这里提供了跨平台标准化实现。需要定制Allocator的话可以把特化 std::allocator_traits<Allocator> 继承这个辅助类然后仅仅定制化自己差异的部分就行了。

大家有需要可以自取: https://github.com/owent/atframe_utils/blob/main/include/memory/allocator_traits.h

按对象类型的内存统计模块

在内存统计和分析模块里,我们是需要对类型自动插桩。所以分析统计的不是像 jemalloc+jeperf 或者 tcmalloc+gperf 那种基于malloc/free的。而是基于类型的。 这当中我们必然需要一些数据块来记录统计结果。那么为了减少这个开销,我们采用和类型相关的static变量的方式。简单show一下codes就是:

template <class T>
struct helper {
  static object_allocator_metrics_storage* get_instance() {
    static bool object_statistics_destroyed = false;
    static object_allocator_metrics_storage* object_statistics_inst = mutable_object_allocator_metrics_for_type(
        try_parse_raw_name(
            guess_raw_name<typename ::std::remove_reference<typename ::std::remove_cv<T>::type>::type>()),
        try_parse_demangle_name(
            guess_pretty_name<typename ::std::remove_reference<typename ::std::remove_cv<T>::type>::type>()),
        sizeof(typename ::std::remove_reference<typename ::std::remove_cv<T>::type>::type),
        object_statistics_destroyed);
    if (object_statistics_destroyed) {
      return nullptr;
    }
    return object_statistics_inst;
  }
};

template <class U>
static void add_constructor_counter_template(void* p) {
  if (nullptr != p) {
    add_constructor_counter(helper<U>::get_instance(), p);
  }
}

这里有个特殊的 object_statistics_destroyed 使用用于在退出阶段可能也会产生内存分配,这时候统计模块可能已经退出了。这时候就不需要统计了,不然反而循环创建销毁反而会有问题。 所有的单例模式实现如果涉及交叉引用也会有类似的问题,这又是属于另一个话题了。

boost某些容器实现的问题

理想情况下,所有标准化容器的实现我们都可以通过重定向Allocator来统计分析内存分配和构造析构。但是实际引用场景中,我们会发现很多(开源)组件其实并没有走这种完整的Allocator接入方式。 举个例子,在 boost::share_ptr 中,底层实际上也是会创建带引用计数相关的对象 boost::detail::sp_counted_impl_p , boost::detail::sp_counted_impl_pd , boost::detail::sp_counted_impl_pda 等等。 这里面充斥着一些没有走 rebind_alloc/rebind 的直接 new/deleteplacement new , 直接调用析构函数。这会导致一些统计miss掉。 所以实际实践中,特别是构造和析构,最好是不要依赖成对出现。

Demangle的小trick和 NO RTTI 的读取符号实现

C++的原始符号是比较难看的,可读性好的符号。可以通过原始符号使用接口 demangle 出来。但是不同平台的方式不一样,也涉及动态内存分配。为了优化这个问题,我们采用了一个曲线救国的方案。

简单得说,对于函数模板,MSVC有 __FUNCSIG__ 宏,GCC和Clang有 __PRETTY_FUNCTION__ 宏可以取到 Demangle 后的名字。 大体上这个名字规则就是对于 template <class T> static const char* guess_pretty_name() 预定义宏的名字规则大致是 guess_pretty_name() [with T = 实际类型], guess_pretty_name() [T = 实际类型]guess_pretty_name<实际类型>(void) ,然后我们根据不同的编译器剔除规则即可。 为了方便理解,直接贴一段剔除代码:

#include <iostream>
#include <string>
#include <type_traits>

namespace atframework {
namespace memory {
    
class object_allocator_metrics_controller {
 public:
  template <class T>
  static const char* guess_raw_name() {
#  if defined(_MSC_VER)
    return typeid(T).raw_name();
#  else
    return typeid(T).name();
#  endif
  }

  template <class T>
  static const char* guess_pretty_name() {
#if defined(_MSC_VER)
    return __FUNCSIG__;
#else
    return __PRETTY_FUNCTION__;
#endif
  }
};

}
}

static const char* skip_space(const char* input) {
  if (nullptr == input) {
    return nullptr;
  }

  while (*input && (' ' == *input || '\t' == *input || '\r' == *input || '\n' == *input)) {
    ++input;
  }

  return input;
}

static const char* find_char(const char* input, const char c) {
  if (nullptr == input) {
    return nullptr;
  }

  while (*input && *input != c) {
    ++input;
  }

  return input;
}

std::string try_parse_demangle_name(const char* input) {
  if (nullptr == input) {
    return {};
  }

  if (!*input) {
    return {};
  }

  const char* start = input;
  while (*start) {
    if (*start == '<' || *start == '[') {
      break;
    }

    ++start;
  }

  // Unknown pretty name, use origin for fallback
  do {
    if (!*start) {
      break;
    }

    // Parse guess_pretty_name() [with T = XXX]
    const char open_symbol = *start;
    const char close_symbol = open_symbol == '[' ? ']' : '>';

    const char* begin;
    if (*start == '[') {
      const char* find_eq = find_char(start, '=');
      if (find_eq && *find_eq == '=') {
        begin = skip_space(find_eq + 1);
      } else {
        begin = skip_space(start + 1);
      }
    } else {
      // Parse guess_pretty_name()<XXX>(void)
      begin = skip_space(start + 1);
    }

    size_t depth = 1;
    const char* end = begin;
    while (*end && depth > 0) {
      if (*end == open_symbol) {
        ++depth;
      } else if (*end == close_symbol) {
        --depth;
        if (depth <= 0) {
            break;
        }
      }
      ++end;
    }

    if (end > begin) {
      return std::string{begin, end};
    }
  } while (false);

  std::string fallback = input;
  std::string::size_type sidx = fallback.find("guess_pretty_name");
  if (std::string::npos != sidx) {
    return fallback.substr(sidx + 17);
  }

  return fallback;
}

namespace atfw = atframework;

template<class T>
void try_get_typename() {
    std::cout<< "===================="<< std::endl;
    std::cout<< "Raw Name: "<< atfw::memory::object_allocator_metrics_controller::guess_raw_name<typename ::std::remove_reference<typename std::remove_cv<T>::type>::type>()<< std::endl;
    std::cout<< "Pretty Name: "<< try_parse_demangle_name(atfw::memory::object_allocator_metrics_controller::guess_pretty_name<typename ::std::remove_reference<typename std::remove_cv<T>::type>::type>())<< std::endl;
}

int main()
{
    try_get_typename<std::string>();
    try_get_typename<int[32]>();
    return 0;
}

这段代码在Clang下的输出是:

====================
Raw Name: NSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE
Pretty Name: std::basic_string<char>
====================
Raw Name: A32_i
Pretty Name: int[32]

在GCC下的输出是:

Raw Name: NSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE
Pretty Name: std::__cxx11::basic_string<char>
====================
Raw Name: A32_i
Pretty Name: int [32]

在MSVC下的输出是:

====================
Raw Name: .?AV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@
Pretty Name: class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> >
====================
Raw Name: .$$BY0CA@H
Pretty Name: int[32]

效果

简单展示下我们的Allocator的基础统计的部分输出:

Construct counterDestructor counterAllocate CountDeallocate CountCostDemangled nameRaw name
1010560Bstd::_Sp_counted_ptr_inplace<std::basic_fstream, atframework::memory::object_allocator_manager::allocator<std::basic_fstream, std::allocator<std::basic_fstream > >, (__gnu_cxx::_Lock_policy)2u>St23_Sp_counted_ptr_inplaceISt13basic_fstreamIcSt11char_traitsIcEEN11atframework6memory24object_allocator_manager9allocatorIS3_SaIS3_EEELN9__gnu_cxx12_Lock_policyE2EE
101088Bstd::__detail::_Hash_node<std::pair<const tgf::product_data_require_key, tgf::product_data_require_key_stat>, false>NSt8__detail10_Hash_nodeISt4pairIKN3tgf24product_data_require_keyENS2_29product_data_require_key_statEELb0EEE
144801448057920Bstd::__detail::_Hash_node<std::pair<const std::pair<int, int>, std::vector<global_order_manager::distribution_order_idx> >, false>NSt8__detail10_Hash_nodeISt4pairIKS1_IiiESt6vectorIN20global_order_manager22distribution_order_idxESaIS6_EEELb0EEE
4096040960288KBstd::__detail::_Hash_node<std::pair<const int, std::unordered_map<tgf::product_data_require_key, tgf::product_data_require_key_stat, tgf::trade_api::product_data_require_key_hash_type, tgf::trade_api::product_data_require_key_equal_type, atframework::memory::object_allocator_manager::allocator<std::pair<const tgf::product_data_require_key, tgf::product_data_require_key_stat>, std::allocator<std::pair<const tgf::product_data_require_key, tgf::product_data_require_key_stat> > > > >, false>NSt8__detail10_Hash_nodeISt4pairIKiSt13unordered_mapIN3tgf24product_data_require_keyENS4_29product_data_require_key_statENS4_9trade_api34product_data_require_key_hash_typeENS7_35product_data_require_key_equal_typeEN11atframework6memory24object_allocator_manager9allocatorIS1_IKS5_S6_ESaISF_EEEEELb0EEE
65740657402157KBstd::__detail::_Hash_node<std::pair<const tgf::DTradeMarketProductKey, tgf::product_stats_detail_info_by_cycle>, false>NSt8__detail10_Hash_nodeISt4pairIKN3tgf22DTradeMarketProductKeyENS2_34product_stats_detail_info_by_cycleEELb0EEE
598829942994047904Bglobal_order_manager::slot_data_order_type_db_slice_setN20global_order_manager33slot_data_order_type_db_slice_setE
737373730Bstd::__detail::_Hash_node<std::pair<const std::pair<int, int>, global_order_manager::pending_calculate_product_info>, false>NSt8__detail10_Hash_nodeISt4pairIKS1_IiiEN20global_order_manager30pending_calculate_product_infoEELb0EEE
11110Bstd::_Sp_counted_ptr_inplace<logic_hpa_pull_internal_record_data, atframework::memory::object_allocator_manager::allocator<logic_hpa_pull_internal_record_data, std::allocator<logic_hpa_pull_internal_record_data> >, (__gnu_cxx::_Lock_policy)2u>St23_Sp_counted_ptr_inplaceI35logic_hpa_pull_internal_record_dataN11atframework6memory24object_allocator_manager9allocatorIS0_SaIS0_EEELN9__gnu_cxx12_Lock_policyE2EE
878764Bstd::__detail::_Hash_node<std::pair<const std::pair<int, int>, std::map<int, std::map<int, std::vectortgf::DTradeMarketProductKey, std::greater > > >, false>NSt8__detail10_Hash_nodeISt4pairIKS1_IiiESt3mapIiS4_IiSt6vectorIN3tgf22DTradeMarketProductKeyESaIS7_EESt7greaterIiESaIS1_IKiS9_EEESt4lessIiESaIS1_ISC_SF_EEEELb0EEE
878764Bstd::__detail::_Hash_node<std::pair<const std::pair<int, int>, std::map<int, std::map<long int, std::vectortgf::DTradeMarketProductKey, std::greater > > >, false>NSt8__detail10_Hash_nodeISt4pairIKS1_IiiESt3mapIiS4_IlSt6vectorIN3tgf22DTradeMarketProductKeyESaIS7_EESt7greaterIlESaIS1_IKlS9_EEESt4lessIiESaIS1_IKiSF_EEEELb0EEE
4220192Blogic_hpa_pull_internal_result_data35logic_hpa_pull_internal_result_data
112946549235647303529KBtgf::trade_product_sp_info_summaryN3tgf29trade_product_sp_info_summaryE
56473549235647354923145KButil::v2006::memory::__rc_ptr_counted_data_inplace_alloc<tgf::trade_product_sp_info_summary, atframework::memory::object_allocator_manager::allocator<tgf::trade_product_sp_info_summary, std::allocatortgf::trade_product_sp_info_summary > >N4util5v20066memory35__rc_ptr_counted_data_inplace_allocIN3tgf29trade_product_sp_info_summaryEN11atframework6memory24object_allocator_manager9allocatorIS4_SaIS4_EEEEE
1550155013640Bstd::__detail::_Hash_node<std::pair<const tgf::DTradeStandardPriceKey, std::map<long int, util::v2006::memory::strong_rc_ptrtgf::trade_product_sp_info_summary > >, false>NSt8__detail10_Hash_nodeISt4pairIKN3tgf22DTradeStandardPriceKeyESt3mapIlN4util5v20066memory13strong_rc_ptrINS2_29trade_product_sp_info_summaryEEESt4lessIlESaIS1_IKlSB_EEEELb0EEE
2121112Bstd::__detail::_Hash_node<std::pair<const std::pair<int, int>, global_order_manager::calculate_pool_cycle_cache>, false>NSt8__detail10_Hash_nodeISt4pairIKS1_IiiEN20global_order_manager26calculate_pool_cycle_cacheEELb0EEE
20102176KBglobal_order_manager::slot_data_group_typeN20global_order_manager20slot_data_group_typeE
Allocator和不分内存统计的具体实现可以参见: https://github.com/atframework/atsf4g-co/blob/sample_solution/atframework/service/component/memory/object_allocator_manager.h

后续也会根据需要酌情开源和增加malloc trace, 跟踪点diff, 动态规则的开关跟踪trace等功能。

欢迎有兴趣的小伙伴互相交流研究。