计算无序映射占用的内存空间

Calculating memory space occupied by unordered maps

本文关键字:内存 空间 无序 映射 计算      更新时间:2023-10-16

我有两个无序映射:(代码在linux中执行)

第一个无序映射:

至少包含65536个条目。每个条目由

组成
int
unsigned char
unsigned char

第二个无序映射:

小于65536个条目。每个条目由

组成
int
int
int
vector <char>

现在我想根据上面两个无序映射所占用的内存(以字节为单位)对两者进行比较。之后我想计算内存压缩实现。请指导我如何找到这两张无序地图占用的内存?

第二个无序映射的更多细节:

typedef std::tuple<int, int> key_t;
struct KeyHasher
{
  std::size_t operator()(const key_t& k) const
  {
      using boost::hash_value;
      using boost::hash_combine;
      // Start with a hash value of 0    .
      std::size_t seed = 0;
      // Modify 'seed' by XORing and bit-shifting in
      // one member of 'Key' after the other:
      hash_combine(seed,hash_value(std::get<0>(k)));
      hash_combine(seed,hash_value(std::get<1>(k)));
      // Return the result.
      return seed;
  }
};
struct Ndata
{
int value;
vector<char> accept ;
};
typedef boost::unordered_map<const key_t,Ndata,KeyHasher> SecondMap;
}

如果不查看STL使用的精确unordered_map实现,我不认为可以精确地回答您的问题。

然而,基于unordered_map接口,您可以做出适当的有根据的猜测:

一个unordered_map需要存储:

  • 一个bucket容器(可能是一个矢量结构)

  • max_bucket_count bucket(可能是单链表结构)

  • 每个条目都有一个完整的条目(不仅是值,还有键,以处理键哈希冲突)

在快速查看libc++实现之后,您还需要存储空间:

  • 哈希函数对象

  • 相等性测试函数对象

  • 分配器函数对象

考虑到这一点,我的估计是这样的:

typedef unordered_map<K, V, ...> tMyMap;
size_t getMemoryUsage(const tMyMap& map) {
  auto entrySize = sizeof(K) + sizeof(V) + sizeof(void*);
  auto bucketSize = sizeof(void*);
  auto adminSize = 3 * sizeof(void*) + sizeof(size_t);
  auto totalSize = adminSize + map.size() * entrySize + map.max_bucket_count() * bucketSize();
  return totalSize;
}

这只适用于第一种情况,因为在第二种情况下,根据每个向量的大小,每个条目的内存使用可能完全不同。对于第二种情况,您必须添加如下内容:

size_t getMemoryUsage(const tMyMap& map) {
  auto entrySize = sizeof(K) + sizeof(V) + sizeof(void*);
  auto bucketSize = sizeof(void*);
  auto adminSize = 3 * sizeof(void*) + sizeof(size_t);
  auto totalSize = adminSize + map.size() * entrySize + map.max_bucket_count() * bucketSize();
  auto contentSize = 0;
  for (const auto& kv : map) {
    // since accept is a vector<char>, 
    // it uses capacity() bytes of additional memory
    contentSize += kv.second.accept.capacity();
  }
  totalSize += contentSize;
  return totalSize;
}

然而,考虑到现实世界的分配逻辑,您的map实际使用的内存可能与此有很大不同,例如由于外部碎片。如果您想100%确定unordered_map使用了多少内存,您还需要考虑分配器的行为。

一种可能的方法是通过使用自定义分配器进行实验来找到答案。

一个直接的解决方案可以利用c++ 11的"Scoped allocator model"(参见The Scoped allocator model (Rev 2), Stroustrup: Scoped allocator)。"作用域分配器"将应用于容器,并将递归地应用于容器的元素。

std::unordered_map及其元素提供这样一个自定义分配器将使您能够精确地计算块的数量、大小和总量。

顺便说一下,作用域分配器还可以让您优化内存消耗和性能,例如通过提供"竞技场分配器"。

这里有一个警告:尽管C+11一致性需要作用域分配器模型,但很可能它没有在编译器供应商提供的标准库中实现。例如,clang的标准库为所有容器完全实现了作用域分配器。关于GCC,请阅读这里:GCC状态,以及GCC邮件列表。

编辑:

一个完全不同的方法是使用工具来分析内存消耗。例如,Xcode IDE提供了一个优秀的工具"Instruments",它提供了各种分析方法,其中一个用于跟踪堆和对象分配。比较两个不同的版本只需要几秒钟。当然,可用的工具因您的平台而异;)

我建议创建一个包含您的地图的数组。您将能够创建大量内存(例如1万个),然后可以将总内存(由操作系统显示)除以1万个。当然,您需要考虑开销(分配映射之前的内存消耗)(包括数组)。

执行大量sizeof操作是没有帮助的,因为对于堆上的每个分配,您都有一个实现定义的开销。例如,在Visual Studio 2013中,在Release中分配是通过8个字节的倍数完成的(或者对于大数组是16个字节)。