【问题标题】:Minimizing memory overhead using C++ containers (std::map and std::vector too expensive)使用 C++ 容器最小化内存开销(std::map 和 std::vector 太贵了)
【发布时间】:2014-11-16 21:39:07
【问题描述】:

我预计要处理大量数据记录,其中大约 20 个 uint8_t 键将有数百万个 <int, struct> 对与它们相关联(按 int 排序)。这些对相当轻巧,大约 10 个字节,需要动态分配。

最初,我使用的是std::map<uint8_t, std::vector<int, struct>>,但在研究了与向量相关的开销后,即

中的capacity()

总共3个机器字+sizeof(element) * capacity()

如看到here; capacity()“通常可以容纳两倍于实际数量的元素”,这似乎是有害的。

我可以使用 std::map 来代替向量,但是 ~32 bytes per node 的开销对于此类轻量级对也变得非常昂贵。

我不熟悉 Boost 和其他 C++ 库,所以想知道是否有人可以建议我可以避免手动动态内存分配的解决方案?


编辑:为了澄清以下 cmets 中的几个问题,存储的结构将包含 3 个短裤(开始),并且没有其他数据结构。我预计vector 的长度不超过 1.5*10^8,并且理解为 ~1.4 GiB(感谢@dyp)。

我想问题是,如何管理向量capacity(),以便将通过reserve() 的重新分配保持在最低限度。我也不确定shrink_to_fit()(C++11)的效率

【问题讨论】:

  • 结构中有什么?矢量的典型大小是多少?它有最大值吗?在编译时是否已知?
  • @πάνταῥεῖ 这不只是至少这个数量的元素的建议吗?
  • 您可以推出一个“平面图”,即std::vector<std::pair<key, value>>,并通过在操作前对向量进行排序,您可以使用std::lower_bound 和类似的函数来执行二分查找。在 boost 中可能有一个预制的这样的结构。
  • 数百万对 10 字节对我来说听起来像是几十兆字节。其中 20 个仍然可以是数百兆字节到几千兆字节的数量级。这真的是目标机器的问题吗?
  • 您引用的答案是正确的,但可能会误导您。他基本上是在说容量可能是当前大小的两倍。这只有在增长因子至少为 2 时才会发生,即使这样也只会在重新分配后立即发生。更典型的情况是增长因子为 1.5,增加的面积约为半满,因此浪费的面积约为 25%。我从未见过大于 2 的增长因子。等于 2 曾经很常见,但 1.5 在当前实现中似乎很常见。

标签: c++ c++11 vector map stl


【解决方案1】:

跟进@NielKirk 关于 std::vector 的观点,而不是键的映射,只有 256 种可能性,您还可以考虑使用 std::array (甚至是 C 样式的数组)作为键.

至于 std::pair 元素,初始实现将它们作为 std::vector<:pair struct>> 集合的成员,你说

我可以使用 std::map 代替向量,但是对于这种轻量级对,每个节点约 32 字节的开销也变得非常昂贵。

这意味着元素的 int 部分是唯一的,因为您没有提到 std::multimap。你可以看看谷歌sparsehash (http://code.google.com/p/sparsehash/)。从项目主页:

一个非常节省内存的 hash_map 实现。 2 位/入口开销! SparseHash 库包含几个哈希映射实现,包括优化空间或速度的实现。

这些哈希表实现在 API 上类似于 SGI 的 hash_map 类和 tr1 unordered_map 类,但具有不同的性能特征。在 C++ 代码中,用 sparse_hash_map 或 dense_hash_map 替换 hash_map 或 unordered_map 很容易。

我以前用过,从来没有遇到过问题。您的 uint8_t 键可以索引到 (std::vector/std::array/C-array) 集合 KCH 的哈希图。如果您愿意,您甚至可以将 KCH 定义为对象的集合,每个对象都包含一个 hashmap,因此每个 KCH[i] 都可以实现一个方便的接口来使用std::pair&lt;int, struct&gt;该键的对象。您将有一个“坏键”元素作为集合中非键元素的默认值,它引用 a)单个空的虚拟哈希图或 b)适当处理意外键值的“坏键对象”。

类似这样的:

typedef std::pair<int, struct>                            myPair;
typedef google::sparse_hash_map<int, myPair>              myCollectionType;
typedef google::sparse_hash_map<int, myPair>::iterator    myCollectionIter;

myCollectionType dummyHashMap;
std:array<myCollectionType, 256> keyedArray; 

将所有keyedArray 元素初始化为dummyHashMap,然后为有效键填写不同的哈希映射。

同样,包含对象:

class KeyedCollectionHandler {
public:
    virtual bool whatever(parm);
    ...

private:
    myCollectionType collection;
};

class BadKeyHandler : public KeyedCollectionHandler 
{
public:
    virtual bool whatever(parm){
        // unknown or unexpected key, handle appropriately
    }
    ...
};

BadKeyHandler badKeyHandler;

将 256 个键控数组元素初始化为 badKeyHandler,填写 KeyedCollectionHandler 对象以获得好的键值。

【讨论】:

  • 这个叫 Niel Kirk 的人是谁?
  • 感谢您的出色而彻底的回答 - sparsehash 似乎符合要求。您是否将 sparsehash 的性能与据称更高效的 dense 实现进行了比较?
  • 感谢您的热情回复。不,我没有比较 sparse_ 和 dense_hash_map 的性能。对于我们的需求,空间考虑不是问题,而是性能问题。对于我们所需要的,dense_hash_map 只是为了提高性能而粉碎了 std::map。我希望 sparse_ 实现也会给人留下深刻印象。
  • 这个页面可能也有帮助 - 它有一个指向一些性能数据以及实现细节的链接:sparsehash.googlecode.com/svn/trunk/doc/index.html
  • @frasnian 该链接很棒-谢谢。再次确认 map_fetch 时间有多相似,现在只是为了衡量我的增长表现......
猜你喜欢
  • 2011-01-19
  • 2013-02-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-04-12
  • 2016-05-01
  • 1970-01-01
相关资源
最近更新 更多