基于磁盘的动态内存分配

时间:2009-04-07 04:04:58

标签: c++ memory-management

我有一个程序,我希望能够在磁盘上存储某些数据(动态分配的块),以减少内存使用和持久性。

我的第一个想法是编写我自己的自定义分配器来管理磁盘上的文件内容,但我想看看有哪些替代方案。

我研究了自定义内存分配器和关于对象序列化的主题,但是在调整这些原则来管理文件的地址空间时,存在细微差别,包括好的和坏的。

在这种情况下:

  1. 只能通过IO(读/写)功能访问内存,而不是直接访问

  2. 不存储任何对象(方法/指针),只存储数据。

  3. 文件的大小不是静态的,因此它应该在需要时增长而不是大而静态

  4. 对于我的用途,可以在碎片整理后重新映射现有指针

  5. 由于数据不是固定大小,因此大多数数据库实现似乎都不太合适。

    我问,这个问题的最佳方法是什么?我应该实现一个简单的内存分配器,将文件视为堆吗?

    供参考,我在嵌入式设备上使用C ++。


    编辑:我已经实现了自己的内存管理器,它使用伙伴内存分配和块大小为2的权限。我很满意它是正确的,不泄漏,合并自由块,并且可以做“停止世界”碎片整理。

    问题是,正如预期的那样,存在相当多的内部和外部碎片。我不是这个领域的专家,虽然我发现它很迷人(我还是学生),但我想知道是否还有其他任何实现相同或类似的东西?当然我不能成为唯一一个人?


    一些有用但迄今为止不兼容的主题是:

    mmap tbh我没有使用过mmap但是,它解决了文件IO,但没有解决文件地址空间的管理问题。

    BOOST:serialization我现在有一种(可能是不合理的)不愿意使用boost库。

    STXXL有趣但无法解决可变大小的内存分配问题

    Doug Lea Memory Allocator对内存分配器的问题有很好的见解,但是我无法尝试自己实现

12 个答案:

答案 0 :(得分:8)

  

我已经实现了自己的内存管理器,它使用伙伴内存分配和2的幂的块大小。我很满意它是正确的并且没有泄漏,合并了自由块并且可以做“停止世界”碎片整理。

这是迈出的第一步。一旦你有一个工作的自定义内存分配器,你当然可以做得更好!

  

问题在于,正如预期的那样,存在相当多的内部(2个块的功率)和外部碎片。我不是这个领域的专家,虽然我觉得它很有吸引力(我还是学生),但我想知道是否还有其他任何实现相同或类似的东西?当然我不能成为唯一一个人?

两种力量是一种通用方法。但请注意,这可能不是最好的,因为您的分配模式可能不会遵循相同的几何级数。在这种情况下,最好尽可能多地进行测试,看看哪些块大小分配最多,并相应地进行优化。

我还想提一下Andrei Alexandrescu和Emery Berger关于内存分配的一篇精彩文章:Policy-Based Memory Allocation以及后者的工作:The Hoard Memory Allocator

如果可能,请查看该文章末尾提到的参考文献。他们也可以提供额外的见解。

答案 1 :(得分:8)

您的两个目标是减少内存使用量并保留数据。这听起来像是数据库的工作。但是你说了

  

因为数据不是固定的   大小,大多数数据库实现   似乎不太适合。

我想你会对这个distinctive feature of SQLite(一个非常轻量级的包含公共域源代码的跨平台数据库)感兴趣:

  

可变长度记录

     

...

     相比之下,SQLite只使用实际需要的磁盘空间量   将信息存储在一行中。如果   你在一个单位存储一个字符   VARCHAR(100)列,然后只有一个   消耗掉单个字节的磁盘空间。   (实际上是两个字节 - 有一些   每个开头的开销   用于记录其数据类型的列   长度。)

它也是good choice for embedded development

  

嵌入式设备和应用

     

因为SQLite数据库需要   很少或没有管理,SQLite是   设备或服务的不错选择   必须在无人看管的情况下工作   人的支持。 SQLite非常适合   用于手机,PDA,机顶盒   盒子和/或电器。它也是   适用于嵌入式数据库   可下载的消费者应用程序。

答案 2 :(得分:3)

你最好的选择是快key-value store。 RDBMS的优势在于您不需要数据库的所有开销。

答案 3 :(得分:2)

我最近编写了一个虚拟堆类,用于解决我遇到的高内存使用问题。该代码为LGPL,并在code.google.com上托管:

http://code.google.com/p/kgui/source/browse/trunk/vheap.cpp

http://code.google.com/p/kgui/source/browse/trunk/vheap.h

基本上它的工作原理如下:

1)定义要在内存中保留的块大小和块数,以及用于缓存到文件系统的文件名。在我的使用案例中,我随时都有200块1MB的内存。

2)然后调用Allocate来保留一块“虚拟内存”。您将向内存返回一个8字节的“句柄”。如果需要,您可以分配大于块大小的块。

3)要写入“虚拟堆”,有一个写入函数,您可以在其中传递“句柄”,指向数据和数据大小的指针。

4)要从“虚拟堆”中读取,有一个读取函数,您可以在其中传递“句柄”,指向要读取的数据的目标和大小的指针。

代码自动处理内存中的内容与磁盘上存储的内容之间的交换。实际上这很简单。

答案 4 :(得分:1)

对于嵌入式设备,我当然会做一个简单的实现,而不是使用数据库。直接文件IO避免了数据库的一些开销。在嵌入式环境中,资源通常是有限的。

编写内存分配器的想法可能是最好的方法。它应该提供某种API层,以尽可能地将基于文件的内存管理与应用程序的其余部分隔离开来。这样,以后可以很容易地换出(没有双关语)用于不同的实现,因此在需要时进行优化。

答案 5 :(得分:1)

我肯定会使用mmap作为I / O.这样可以轻松直接访问数据并在需要时刷新到磁盘。您唯一需要控制的是文件在地址空间中的映射位置,因此您可以将其移动。

内存管理的一种可能性是为每个对象创建不同的文件,并使用文件系统级碎片整理而不是自己实现。您从未提及您正在使用的操作系统/文件系统,但如果它已经在线碎片整理,我会使用它。如果您使用的是Linux并且可以使用XFS,则可以使用xfs_fsr。我希望文件系统碎片整理能够得到高度优化,而且只需要在一个大文件中自行实现就可以节省很多精力。

答案 6 :(得分:1)

据我所知,您需要一个文件系统而不是内存分配系统。首先,在嵌入式系统中,磁盘中的动态内存分配是一个矛盾的术语。用于持久存储的磁盘(硬盘或闪存设备)与内存有很大不同。它不仅是您访问它的方式,而且是磁盘存储不是100%可靠的事实。写入磁盘时,您需要有一个避免坏扇区的算法。您是否想过这个或者您认为您的磁盘故障是免费的吗?

文件系统将处理空间分配和坏扇区问题。 FAT通常用于嵌入式设备。虽然FAT的碎片性能相当差,但这并没有阻止它在许多嵌入式设备中使用。大多数基于闪存的设备实际上都使用FAT。

无论如何,我建议从你现在拥有的东西开始:你的操作系统(如果你使用的话)和磁盘的驱动程序。调查是否已经从这些解决方案支持合适的解决方案。另外请记住,嵌入式设备更难以调试 - 如果您设置实现自己的算法需要更长的开发时间。

答案 7 :(得分:1)

看看HDF5 http://www.hdfgroup.org/HDF5/whatishdf5.html

这应该符合您的目的。

答案 8 :(得分:0)

我认为使用简单的堆分配器可以减少内部碎片。您只需分配实际使用的内存量(加上标头的开销)。如果你已经辞职去做世界末日的压缩,你可以将它与新的竞技场分配相结合,并分配一个新的(更大的)竞技场并将你所有的现场块复制到新的竞技场。

答案 9 :(得分:0)

我要回复kgiannakakis - 您所描述的是文件系统,而不是内存管理系统。

由于您的所有访问都是通过I / O函数进行的,因此您的对象不必在磁盘上连续。不是将每个对象放入动态大小的块中,而是将对象划分为多个固定大小的块。这些块可以位于任何地方,您只需要将它们链接在一起。您的I / O功能将根据需要分解并合并块。

答案 10 :(得分:0)

Hmmh。这听起来像是BDB(Berkeley DB)的一个非常常见的用例。它是一个高效的生产质量库,可以执行键值持久性“数据库”(〜=与其他数据库的表),开源和所有。

我认为关系(SQL)数据库没有多大意义,但bdb等人(gnu db和我确定还有其他人)肯定会这样做。

答案 11 :(得分:0)

您可能希望查看Boost.Interprocess提供的功能,特别是查看托管内存映射文件工具。