数据结构存储大量数据?

时间:2010-11-09 07:03:58

标签: c++ memory data-structures dynamic-memory-allocation

在我的应用程序中,我必须从图像集(MRC图像)加载volumedata并将像素数据保存在内存中。(图像是灰度级的,因此每像素一个字节)。

我的开发环境是QT框架,适用于Windows的MinGW和适用于Linux的GCC。

目前,我使用简单的数据结构将volumedata存储为:

unsigned char *volumeData;

并按如下方式进行大量分配。

volumeData=new unsigned char[imageXsize * imageYsize * numofImages];

以下是访问给定平面中图像数据的重要方法,例如

unsigned char* getXYPlaneSlice(int z_value);
unsigned char* getYZPlaneSlice(int x_value);
unsigned char* getZXPlaneSlice(int y_value);

使用我简单的数据结构,很容易实现上述方法。

但是我们未来可能需要采用体积大小为2000x2000x1000(~3.7Gb)。当前的数据结构将无法处理那些庞大的数据。

  1. 如何避免碎片?现在,即使使用1000x1000x200数据,应用程序也会崩溃,从而导致bad_alloc。 什么是更改数据结构的最佳方法?我应该使用链接列表,每个块大小为100mb。

  2. 此外,用户应该能够在体积数据上执行一些图像处理过滤器,并且还应该能够重置为原始像素值。 这意味着,我应该保留两份卷数据。 与目前的实施类似。

    unsigned char * volumeDataOriginal;

    unsigned char * volumeDataCurrent;

  3. 因此,对于2000x2000x1000数据范围,它将使用大约8Gb(每个音量4Gb)。 但在Win32中,地址空间为4GB。如何解决这个问题?我应该使用64位应用程序吗?

    编辑: 以下是我的应用程序enter image description here

    的快照

    基本上,我加载了体积数据(来自MRC格式的图像集),并将它们显示在不同的平面查看器中(XY,YX,YZ。图像显示XY平面查看器)。我需要保持以上3种数据访问方法以在特定平面中显示图像。使用滑块栏用户可以更改在所选平面中显示的图像)

    提前致谢。

11 个答案:

答案 0 :(得分:14)

我认为你应该看看hdf5。这是一种二进制格式,用于存储从望远镜,物理实验和基因测序机器等收集的大量数据。使用这样的东西的好处很多,但有三个直接的想法是:(1)测试,(2)支持hyperslab选择,(3)你免费获得压缩。

有C / C ++,java,python,matlab库可用。

答案 1 :(得分:5)

64位可能是处理此问题的最简单方法...当您使用它们时,让操作系统出现故障。否则,如果不通过数据了解您的访问模式,就很难成功。如果你经常扫描图像以找到相同像素坐标处的值,那么谈论如何指示按需保存和重新加载的图像是毫无意义的。

对于撤消数据,您可以按照建议保留完整备份副本,或者您可以尝试进行撤消操作以查看所做的更改并负责查找有效的实施。例如,如果您只是翻转位,那么这是非破坏性的,您只需要一个仿函数来进行相同的位翻转操作来撤消更改。如果将所有像素设置为相同的色调是一种常见操作(例如填充,清除),那么您可以使用布尔值和单个像素对该图像状态进行编码,并使用完整缓冲区进行撤消。

答案 2 :(得分:5)

解决问题的最简单方法是使用64位地址空间 - 现代Mac支持开箱即用,在Windows和Linux上,您需要安装64位版本的操作系统。我相信Qt可以很好地用于构建64位应用程序。 32位系统将无法支持您所讨论的大小的单一分配 - 即使具有4 GB可用地址空间的Mac也无法进行单个3.7 GB分配,因为不会是一个相当大的连续空间。

对于撤消,我会考虑使用内存映射文件和copy-on-write来复制块:

http://en.wikipedia.org/wiki/Copy-on-write

这意味着您实际上不必复制所有原始数据,系统会在写入页面时复制页面。如果您的图像比实际内存大得多并且您没有更改图像的每个部分,这将极大地提高性能。看起来boost::map_file具有“私人”访问权限可能对此有所帮助。

如果你真的,真的需要支持32位系统,你唯一的选择就是以某种方式打破那些大块,通常是平面或子卷。在应用3D滤镜等时,两者都很难用,所以如果可以,我真的会避免这种情况。

如果你选择了子卷路径,一个技巧是将所有子卷保存在内存映射文件中,并仅在需要时将它们映射到地址空间。当从地址空间取消映射时,它们应该保留在统一缓冲区高速缓存中直到被清除,这实际上意味着您可以使用比具有地址空间更多的RAM(特别是在Windows上,默认情况下32位应用程序仅获得2 GB的地址空间)

最后,在32位Windows上,您还可以查看boot.ini中的/ 3GB开关。这允许您为应用程序分配3 GB的地址空间,而不是正常的2 GB。从你描述的问题我不认为这会给你足够的地址空间,但它可能会帮助你一些较小的卷。请注意,/ 3GB开关可能会导致某些驱动程序出现问题,因为它会减少内核可用的地址空间量。

答案 3 :(得分:4)

您可以使用内存映射文件来管理内存有限的大型数据集。但是,如果您的文件大小为4GB,则建议使用64位。 boost项目有一个很好的多平台内存映射库,可以非常接近你想要的。

http://en.wikipedia.org/wiki/Memory-mapped_file http://www.boost.org/doc/libs/1_44_0/libs/iostreams/doc/classes/mapped_file.html 让你开始下面的一些示例代码 -

#include <boost/iostreams/device/mapped_file.hpp>
boost::iostreams::mapped_file_source input_source;
input_source.open(std::string(argv[1]));
const char *data = input_source.data();
long size = input_source.size();
input_source.close();

谢谢, 森

答案 4 :(得分:3)

我要考虑的一个选项是内存映射,而不是映射所有图像,维护一个延迟加载的图像的链接列表。当您的过滤器在图像列表中工作时,根据需要加载。在加载阶段,映射相同大小的匿名(或某个固定临时文件)块,并将映像复制为备份。当您应用过滤器时,您只需备份到此副本。正如@Tony上面所说,64位是你最好的选择,对于多平台内存映射文件,请看一下boost interprocess。

答案 5 :(得分:3)

使用STXXL:超大型数据集的标准模板库。

我第一次在SO上听说过:)

答案 6 :(得分:1)

您可以使用两级结构: 指向单个图像的指针数组或(更好)一堆图像。 因此,您可以在一个内存块中保留20个图像,并将指向20个图像块的指针放入阵列中。 在进行随机访问时,这仍然很快(与链表相比)。

然后,您可以实现一个简单的分页算法:首先,数组中的所有指针都是NULL。首次访问图像块时,将该块的20个图像加载到内存中,并将指针写入阵列。 对这些图像的下一次访问不会加载任何内容。

如果您的内存因为加载并加载了许多图像块而变低,那么您可以删除最少使用的图像块(您应该在指针旁边添加第二个字段,在那里放入一个计数器值每次加载图像块时都要计数。具有最低计数器的图像块是最少使用的图像块并且可以被丢弃(内存被重新用于新块并且指针被设置为NULL)。

答案 7 :(得分:1)

目前处理大量数据的趋势是将数据分解为64x64x64的较小数据块。如果你想用光照进行体绘制,那么你应该在相邻的砖块之间有1个体素重叠,这样就可以渲染单个砖块,而不需要相邻的砖块。如果你想用砖块进行更复杂的图像处理,那么你可以增加重叠(以存储为代价)。

这种方法的优点是您只需要将必要的砖块加载到内存中。基于砖的体积的渲染/处理时间并不比非砖块基础体积慢得多。

有关体积渲染方面的更多参与讨论,请查看Octreemizer上的文章。 Here is a link to one on citeseer

答案 8 :(得分:1)

主要问题可能是您希望对您的数据进行全面随机访问

最好的方法是考虑你想要使用的算法,并且不能写出主要跨越 one 的数据方向。好吧,那并不总是可能的。

如果你想自己编写一个中等重量的解决方案,你应该这样做:

  • 使用mmap()将数据结构的切片映射到内存
  • 将数据封装在类中,以便您可以捕获对当前非映射数据的访问权
  • mmap()按需要的区域,然后。

(实际上,这就是操作系统正在做的事情,如果你一次mmap()整个文件,但是通过一些控制,你可以按需 算法更智能,随着时间的推移,并符合您的要求)。

再次,如果你跳过这些图像体素,这就没有意思了。 您的算法必须符合数据访问 - 适用于您选择存储数据的每个解决方案。 随机访问将“破坏”所有内容,如果您的数据大于您的物理内存。

答案 9 :(得分:1)

如果硬件和操作系统允许,我会去64位,并将文件映射到内存(请参阅Windows上的CreateFileMapping和Linux上的mmap)。

在Windows上,您可以查看允许写入时复制的映射文件。我相信你也可以在Linux上获得这个功能。无论如何,如果您在源文件上创建只读视图,那么这将是您的“原始数据”。然后在源文件上创建一个写时复制视图 - 这将是“当前数据”。

修改当前数据时,将为您复制和分配修改后的基础页面,源数据的页面将保持不变。如果确保不将相同的数据写入“当前数据”,则还可以获得最佳的内存使用率,因为当前数据和原始数据将共享内存页。但是,您必须考虑页面对齐,因为写入时复制是基于页面的。

此外,从当前数据恢复到原始数据是一项简单的工作。您需要做的就是重新创建“当前数据”的映射。

通过使用文件映射,管理内存的繁琐工作将由操作系统处理。它将能够以非常有效的方式使用所有可用内存。比普通堆分配更有效的方法。

我首先研究CreateFileView()和MapViewOfFile()以便在Windows上使用。对于Linux你有mmap(),但就我所知而言。自2000年以来我没有触及任何* nix ......

答案 10 :(得分:0)

看看SciDB。我不是它的专家,但是从它的sample use casesa paper describing it,它允许你自然地将你的数据映射到3D(+ 1D for time / versioning)数组,如下所示:

CREATE ARRAY Pixels [
    x INT,
    y INT,
    z INT,
    version INT
] (
    pixel INT
);

并实施您的查询getXYPlaneSlice

Slice (Pixels, z = 3, version = 1);

为了避免在仅更改部分数据时重复数据,因为SciDB支持稀疏数组,所以不需要为版本1填充整个数组。然后,当您需要加载最新数据时,可以使用version = 0加载旧版本,并使用version = 1的其他加载更新结果。