繁体   English   中英

std :: deque的内存开销是怎么回事?

[英]What the heque is going on with the memory overhead of std::deque?

我正在研究一种使用std::queue的外部排序算法,并且必须小心地限制其内存使用量。 我注意到在合并阶段(使用固定长度的几个std::queue ),我的内存使用量增加到我预期的2.5倍左右。 由于std::queue默认使用std::deque作为其底层容器,因此我在std::deque上运行了一些测试以确定其内存开销。 以下是在发布模式下在VC ++ 9上运行的结果,具有64位进程:

std::deque添加100,000,000个char ,内存使用量增加到252,216K。 请注意,100M char (1字节)应占用97,656K,因此这是154,560K的开销。

我用double s(8字节)重复测试,看到内存增长到1,976,676K,而100M double s应占用781,250K,开销为1,195,426K!

现在我明白std::deque通常是作为“块”的链表实现的。 如果这是真的,那么为什么开销与元素大小成比例(因为指针大小当然应该固定在8个字节)? 为什么这么大呢?

任何人都可以解释为什么std::deque使用如此多的危险记忆? 我想我应该将我的std::queue底层容器切换到std::vector因为没有开销(假设适当的大小是reserve )。 我认为std::deque的好处在很大程度上取决于它有如此巨大的开销(导致缓存未命中,页面错误等),并且复制std::vector元素的成本可能是更少,因为整体内存使用率低得多。 这只是微软对std::deque的糟糕实现吗?

查看_DEQUESIZ的代码(每个块的元素数):

#define _DEQUESIZ   (sizeof (_Ty) <= 1 ? 16 \
    : sizeof (_Ty) <= 2 ? 8 \
    : sizeof (_Ty) <= 4 ? 4 \
    : sizeof (_Ty) <= 8 ? 2 : 1)    /* elements per block (a power of 2) */

如果元素更大,它会变小。 只有大于8个字节的元素才能获得预期的行为(随着元素大小的增加,开销的百分比减少)。

您是否可能正在运行Debug二进制文件? 100M字符的252MB看起来确实很多......

您可以使用umdh检查之前和之后的快照,然后比较两者 - 可能会说明为什么它比您预期的更大。

编辑:仅供参考 - 当我在VS2010上的调试器外运行时,我得到181MB的char

deque<char> mydequeue;
for (size_t i = 0; i < 100 * 1024 * 1024; ++i)
{
  mydequeue.push_back(char(i));
}

编辑:支持@Dialecticus的其他答案,这给了我与double相同的足迹:

struct twoInt64s
{
public:
    twoInt64s(__int64 _a, __int64 _b) : a(_a), b(_b) {}

    __int64 a;
    __int64 b;
};

编辑:如图所示修改_DEQUESIZ (每个块128个字符),100M字符现在占用113M内存。

我的结论是,您看到的剩余开销是由于deque块的管理结构,其中包含16个字符的数据,以及deque控制信息以及堆管理器的更多控制信息。

#define _DEQUESIZ   (sizeof (value_type) <= 1 ? 128 \
    : sizeof (value_type) <= 2 ? 8 \
    : sizeof (value_type) <= 4 ? 4 \
    : sizeof (value_type) <= 8 ? 2 \
    : 1)    /* elements per block (a power of 2) */

道德 - 如果你真的想为了你的特殊目的而优化它,那就准备和<deque>一起玩吧。 它的行为主要取决于元素的大小,超出预期使用模式的大小。

编辑:根据您对队列大小的了解,您可以将boost :: circular_buffer作为std :: queue容器的替代品。 我打赌这会表现得更像你想要的(和预期的)。

在没有查看你正在使用的std :: queue的实际实现的情况下,我的猜测是它的内存分配看起来像这样:

if (new element won't fit) {
    double the size of the backing storage
    realloc the buffer (which will probably copy all elements)
}

加倍而不是更保守的原因是你希望queue.push_pack操作的平均时间为O(1)。 由于重新分配可能会复制现有元素,因此在您最初将所有值都推入队列时,只需要根据需要生成数组的版本(一次一个元素)将为O(n ^ 2)。 我将把它留作读者的练习,如何加倍版本给出恒定的平均时间。

由于您引用的是整个过程的大小,因此当您略微超过2(2 ^ 26 <100MM <2 ^ 27)值的元素时,估计大约2倍的开销似乎是合理的。 尝试在2 ^(n-1)处停止,测量,然后按几个元素并再次测量。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM