我遇到问题我正在处理需要使用某种二维数组的地方。数组是固定宽度(四列),但我需要动态创建额外的行。
要做到这一点,我一直在使用向量的向量,我一直在使用一些包含这个的嵌套循环:
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位)
答案 0 :(得分:6)
这里真正的答案与核心数据结构没什么关系。答案是MSVC对std :: deque的实现特别糟糕,并且退化为指向单个元素的指针数组,而不是它应该是的数组数组。坦率地说,只有两倍的向量内存使用是令人惊讶的。如果你有更好的deque实现,你会得到更好的结果。
答案 1 :(得分:5)
这完全取决于deque
的内部实施(我不会谈论vector
,因为它相对简单。)
事实上,deque
与vector
有完全不同的保证(最重要的是它支持两端的O(1)插入,而vector
仅支持O(1)插在后面)。这反过来意味着由deque
管理的内部结构必须比vector
更复杂。
为了实现这一点,典型的deque
实现会将其内存分成几个非连续的块。但是每个单独的存储器块具有固定的开销以允许存储器管理工作(例如,无论块的大小如何,系统可能还需要另外的16或32字节或者其他任何东西,仅用于簿记)。因为,与vector
相反,deque
需要许多小的独立块,开销堆叠可以解释您看到的差异。还要注意那些单独的内存块需要管理(可能是在不同的结构中?),这可能意味着一些(或很多)额外的开销。
至于一种解决问题的方法,你可以尝试一下@BasileStarynkevitch在评论中提出的建议,这确实会减少你的内存使用量,但它只会让你到目前为止,因为在某些时候你仍然会耗尽内存。如果你试图在只有256MB RAM的机器上运行你的程序怎么办?任何其他解决方案的目标是减少内存占用,同时仍然试图将所有数据保存在内存中,也会遇到同样的问题。
处理大型数据集时的正确解决方案是调整算法和数据结构,以便能够在整个数据集中处理小分区,并根据需要加载/保存这些分区以便制作其他分区的空间。不幸的是,因为它可能意味着磁盘访问,它也意味着性能大幅下降,但嘿,你不能吃蛋糕也有它。
答案 2 :(得分:5)
有效实施双端队列的两种常用方法:使用修改后的动态数组或使用 doubly linked list 。
修改后的动态数组使用的基本上是一个动态数组,可以从两端增长,有时称为数组deques 。这些数组deques具有动态数组的所有属性,例如恒定时间随机访问,良好的引用局部性,以及中间插入/删除效率低,并且添加了分摊的常量时间插入/两端移除,而不仅仅是一端。
修改后的动态数组有几种实现方式:
从底层数组的中心分配deque内容, 并在到达任何一端时调整底层数组的大小。这个 方法可能需要更频繁的调整和浪费更多空间, 特别是仅在一端插入元素时。
将deque内容存储在循环缓冲区中,仅在调整时调整大小 缓冲区变满了。这会降低调整的频率。
将内容存储在多个较小的数组中,分配额外的数组 根据需要在开头或结尾添加数组。索引由。实现 保持包含指向每个较小的指针的动态数组 阵列。
不同的库可能以不同的方式实现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)
主要问题是内存不足。
那么,你是否需要一次内存中的所有数据? 你可能永远无法做到这一点。
您可能需要考虑将数据处理为“块”或更小的子矩阵。例如,使用标准矩形网格:
如果要搜索粒子或一组基准,则无需将整个数据集读入内存即可。
如果您的应用程序从输入源(文件除外)接收原始数据,您将需要存储数据以供以后处理。
这将需要多个缓冲区,并且使用至少两个执行线程更有效。
读取线程将数据读入缓冲区,直到缓冲区已满。当缓冲区已满时,它会将数据读入另一个空数据库。
写入线程最初将等待,直到第一个读取缓冲区已满或读取操作完成。接下来,写入线程将数据从读取缓冲区中取出并写入文件。然后写入线程从下一个读缓冲区开始写入。
此技术称为双缓冲或多缓冲。
如果矩阵中有大量零或未使用的数据,则应尝试使用稀疏矩阵。本质上,这是一个包含数据坐标和值的结构列表。当大多数数据是除零以外的公共值时,这也适用。这节省了大量的内存空间;但是执行时间要多一点。
您还可以更改算法以使用数据压缩。这里的想法是存储数据位置,值和数量或连续的相等值(运行时)。因此,不是存储100个相同值的连续数据点,而是存储起始位置(运行),值和100作为数量。这节省了大量空间,但在访问数据时需要更多的处理时间。
有些库可以将文件视为内存。基本上,他们在文件的“页面”中读入内存。当请求退出“页面”时,他们会在另一页中读取。所有这些都是在“幕后”进行的。您需要做的就是将文件视为内存。
阵列和deques不是您的首要问题,数据量是多少。您可以通过一次处理小块数据,压缩数据存储或将文件中的数据视为内存来解决主要问题。如果您正在尝试处理流数据,请不要。理想情况下,流数据应放入文件中,然后再处理。 文件的历史目的是包含不适合内存的数据。