为什么deque在C ++中使用比vector更多的RAM?

时间:2013-04-27 12:37:31

标签: c++ visual-c++ vector ram deque

我遇到问题我正在处理需要使用某种二维数组的地方。数组是固定宽度(四列),但我需要动态创建额外的行。

要做到这一点,我一直在使用向量的向量,我一直在使用一些包含这个的嵌套循环:

array.push_back(vector<float>(4));
array[n][0] = a;
array[n][1] = b;
array[n][2] = c;
array[n][3] = d;
n++

添加行及其内容。问题是我似乎因为我试图创建的元素数量而耗尽内存,所以我减少了我使用的数量。但后来我开始阅读deque,并认为它可以让我使用更多的内存,因为它不必是连续的。在这个循环中,我将所有提到的“vector”改为“deque”,以及所有声明。但后来看来我再次耗尽内存,这次甚至还有减少的行数。

我查看了我的代码使用了多少内存,当我使用deque时,内存稳定上升到2GB以上,程序很快关闭,即使使用较少的行数。当内存耗尽时,我不确定这个循环的确切位置。

当我使用向量时,即使循环退出,内存使用(对于相同的行数)仍然低于1GB。然后继续进行类似的循环,添加更多行,仍然只达到约1.4GB。

所以我的问题是。对于deque来说,使用两倍以上的向量内存是正常的,还是我在思考我可以在声明/初始化和上面的代码中用“deque”替换单词“vector”时做出错误的假设?

提前致谢。

我正在使用: MS Visual C ++ 2010(32位) Windows 7(64位)

5 个答案:

答案 0 :(得分:6)

这里真正的答案与核心数据结构没什么关系。答案是MSVC对std :: deque的实现特别糟糕,并且退化为指向单个元素的指针数组,而不是它应该是的数组数组。坦率地说,只有两倍的向量内存使用是令人惊讶的。如果你有更好的deque实现,你会得到更好的结果。

答案 1 :(得分:5)

这完全取决于deque的内部实施(我不会谈论vector,因为它相对简单。)

事实上,dequevector有完全不同的保证(最重要的是它支持两端的O(1)插入,而vector仅支持O(1)插在后面)。这反过来意味着由deque管理的内部结构必须比vector更复杂。

为了实现这一点,典型的deque实现会将其内存分成几个非连续的块。但是每个单独的存储器块具有固定的开销以允许存储器管理工作(例如,无论块的大小如何,系统可能还需要另外的16或32字节或者其他任何东西,仅用于簿记)。因为,与vector相反,deque需要许多小的独立块,开销堆叠可以解释您看到的差异。还要注意那些单独的内存块需要管理(可能是在不同的结构中?),这可能意味着一些(或很多)额外的开销。

至于一种解决问题的方法,你可以尝试一下@BasileStarynkevitch在评论中提出的建议,这确实会减少你的内存使用量,但它只会让你到目前为止,因为在某些时候你仍然会耗尽内存。如果你试图在只有256MB RAM的机器上运行你的程序怎么办?任何其他解决方案的目标是减少内存占用,同时仍然试图将所有数据保存在内存中,也会遇到同样的问题。

处理大型数据集时的正确解决方案是调整算法和数据结构,以便能够在整个数据集中处理小分区,并根据需要加载/保存这些分区以便制作其他分区的空间。不幸的是,因为它可能意味着磁盘访问,它也意味着性能大幅下降,但嘿,你不能吃蛋糕也有它。

答案 2 :(得分:5)

理论


有效实施双端队列的两种常用方法:使用修改后的动态数组或使用 doubly linked list

修改后的动态数组使用的基本上是一个动态数组,可以从两端增长,有时称为数组deques 。这些数组deques具有动态数组的所有属性,例如恒定时间随机访问,良好的引用局部性,以及中间插入/删除效率低,并且添加了分摊的常量时间插入/两端移除,而不仅仅是一端。

修改后的动态数组有几种实现方式:

  1. 从底层数组的中心分配deque内容, 并在到达任何一端时调整底层数组的大小。这个 方法可能需要更频繁的调整和浪费更多空间, 特别是仅在一端插入元素时

  2. 将deque内容存储在循环缓冲区中,仅在调整时调整大小 缓冲区变满了。这会降低调整的频率。

  3. 将内容存储在多个较小的数组中,分配额外的数组 根据需要在开头或结尾添加数组。索引由。实现 保持包含指向每个较小的指针的动态数组 阵列。

  4. 结论


    不同的库可能以不同的方式实现deques,但通常是修改的动态数组。很可能您的标准库使用方法#1来实现std::deque,而因为您只从一端追加元素,最终浪费了大量空间。出于这个原因,它会让std::deque占用比平时更多的空间std::vector

    此外,如果std::deque将被实现为双向链表,那么由于每个元素除了自定义数据之外还需要容纳2个指针,因此也会浪费空间。

    使用方法#3(也修改了动态数组方法)的实现将再次导致浪费空间以容纳其他元数据,例如指向所有这些小数组的指针。

    无论如何,std::deque在存储方面的效率低于普通的std::vector。在不知道您想要实现什么的情况下,我无法自信地建议您需要哪种数据结构。但是,你似乎甚至不知道什么是deques,因此,你真正想要的是std::vector。一般来说,Deques有不同的应用。

答案 3 :(得分:3)

Deque可以在向量上有额外的内存开销,因为它由几个块而不是连续的块组成。

来自en.cppreference.com/w/cpp/container/deque

  

std::vector相反,deque的元素不是连续存储的:典型的实现使用一系列单独分配的固定大小的数组。

答案 4 :(得分:1)

主要问题是内存不足。

那么,你是否需要一次内存中的所有数据? 你可能永远无法做到这一点。

部分处理

您可能需要考虑将数据处理为“块”或更小的子矩阵。例如,使用标准矩形网格:

  • 读取第一象限的数据。
  • 第一个quandrant的处理数据。
  • 存储第一个quandrant的结果(在文件中)。
  • 重复剩余的quandrants。

搜索

如果要搜索粒子或一组基准,则无需将整个数据集读入内存即可。

  1. 分配内存块(数组)。
  2. 将部分数据读入此内存块。
  3. 搜索数据块。
  4. 重复步骤2和3,直到找到数据。
  5. 流数据

    如果您的应用程序从输入源(文件除外)接收原始数据,您将需要存储数据以供以后处理。

    这将需要多个缓冲区,并且使用至少两个执行线程更有效。

    读取线程将数据读入缓冲区,直到缓冲区已满。当缓冲区已满时,它会将数据读入另一个空数据库。

    写入线程最初将等待,直到第一个读取缓冲区已满或读取操作完成。接下来,写入线程将数据从读取缓冲区中取出并写入文件。然后写入线程从下一个读缓冲区开始写入。

    此技术称为双缓冲或多缓冲。

    稀疏数据

    如果矩阵中有大量零或未使用的数据,则应尝试使用稀疏矩阵。本质上,这是一个包含数据坐标和值的结构列表。当大多数数据是除零以外的公共值时,这也适用。这节省了大量的内存空间;但是执行时间要多一点。

    数据压缩

    您还可以更改算法以使用数据压缩。这里的想法是存储数据位置,值和数量或连续的相等值(运行时)。因此,不是存储100个相同值的连续数据点,而是存储起始位置(运行),值和100作为数量。这节省了大量空间,但在访问数据时需要更多的处理时间。

    内存映射文件

    有些库可以将文件视为内存。基本上,他们在文件的“页面”中读入内存。当请求退出“页面”时,他们会在另一页中读取。所有这些都是在“幕后”进行的。您需要做的就是将文件视为内存。

    摘要

    阵列和deques不是您的首要问题,数据量是多少。您可以通过一次处理小块数据,压缩数据存储或将文件中的数据视为内存来解决主要问题。如果您正在尝试处理流数据,请不要。理想情况下,流数据应放入文件中,然后再处理。 文件的历史目的是包含不适合内存的数据。