[英]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.