将大量二进制数据加载到内存中

3
我的应用程序需要将数兆字节到数十吉字节的二进制数据(多个文件)加载到RAM中。经过一些搜索,我决定使用std::vector<unsigned char>来实现这个目的,尽管我不确定它是否是最佳选择。
我将为每个文件使用一个向量。由于应用程序先前知道文件大小,因此会调用reserve()来为其分配内存。有时应用程序可能需要完全读取文件,在其他情况下只需要部分读取,并且向量的迭代器非常适合此类操作。它可能需要从RAM中卸载文件并放入其他文件,std::vector::swap()std::vector::shrink_to_fit()将非常有用。我不想处理低级内存分配(否则将使用C语言)。
我有一些问题:
  • 应用程序必须从列表中加载更多的文件到RAM中。如果要加载另一个文件,它该如何知道是否有足够的内存空间?它应该调用reserve()并查找错误吗?怎么做?参考文献仅表示在请求的大小大于std::vector::max_size时,reserve()会抛出异常。
  • std::vector<unsigned char>是否适用于将这么大量的二进制数据加载到RAM中?我担心std::vector::max_size,因为其参考文献说它的值将取决于系统或实现限制。我认为系统限制是可用RAM,是吗?所以没有问题。但是,如果有关于实现的任何限制会阻止我想要的操作怎么办?如果是,请给我提供一种替代方案。
  • 如果我想使用除N吉字节外的所有RAM空间,最好的方法是什么?真的要使用sysinfo()并根据可用RAM推断出是否可以加载每个文件吗?
注:该应用程序的此部分必须尽可能地获得更高的性能(低处理时间/CPU使用率和RAM消耗)。谢谢你的帮助。

4
建议研究使用内存映射文件 - 请参见 https://msdn.microsoft.com/en-us/library/dd997372(v=vs.110).aspx。 - auburg
很少有系统拥有数百GB的RAM。你可以(可能)将这么多数据读入向量中,但大部分数据将存储在交换文件中。 - Martin Bonner supports Monica
而且,更少的系统真正需要将这些千兆字节保留在RAM中。 - xtofl
1
请看 Boost.Iostreams (mapped_file) 和/或 Boost.Interprocess。 - user2672107
1
即使有128G,也无法加载“数百个”,但我同意其他建议,采用mmap的方式。如果你想移植到Windows,boost有相应的库。 - Martin Bonner supports Monica
显示剩余2条评论
2个回答

6

如何知道是否有足够的内存空间来加载另一个文件?

你无法事先知道。将加载过程包装在 try-catch 中。如果内存不足,则会抛出 std::bad_alloc 异常(假设您使用默认分配器)。在加载代码中假定内存足够,并在异常处理程序中处理内存不足。

但是实现限制呢? ... 有关实现方面的任何内容都可能阻止我做想做的事情吗?

您可以在运行时检查 std::vector::max_size 来验证。

如果程序使用 64 位字长编译,则向量具有足够的最大大小,可达数百千兆字节。


该应用程序的这部分必须获得更高的性能。

这与以下内容有冲突:

我不想费力处理低级内存分配问题。

但是,如果低级内存操作确实可以提高性能,您可以将文件映射到内存中。


我在一些SO问题中读到了,对于需要高性能的应用程序,最好避免使用异常处理,而是更喜欢使用返回值、errno等。但如果你使用标准容器,非抛出式内存分配不是一个选择。如果你对异常过敏,那么你必须使用另一种实现vector的方式——或者你决定使用的任何容器。但是,使用mmap时不需要任何容器。
处理异常会影响性能吗?幸运的是,在运行时,异常的成本与从磁盘读取数百GB相比微不足道。
可能更好的方法是在加载文件之前运行sysinfo()并检查空闲RAM吗?sysinfo调用可能比处理异常慢(我没有测量过,这只是一个推测),而且它不会告诉您可能存在的进程特定限制。
此外,重复尝试加载文件、捕获异常并尝试加载较小的文件似乎很困难和昂贵(需要递归)?不需要递归。如果你喜欢,你可以使用它;它可以用尾递归编写,可以进行优化。
关于内存映射:我曾经看过一些相关内容,但觉得很枯燥。需要使用C的open()等函数,不能使用std::fstream。一旦你映射了内存,就比std::fstream更容易使用。你可以跳过将数据复制到vector的步骤,直接使用映射的内存,就像它已经存在于内存中的数组一样。
似乎使用std::fstream部分读取文件的最佳方法是派生std::streambuf类。
我不明白为什么需要派生任何东西。只需使用std::basic_fstream :: seekg()跳转到要读取的部分即可。

处理异常会影响性能吗?我在一些SO问题中读到,对于需要高性能的应用程序,最好避免使用异常,而是优先处理返回值、errno等,因为当抛出异常时,RAM中存储的副表位于缓慢的“冷”区域。也许更好的方法是运行sysinfo()并在加载文件之前检查可用RAM?这将避免使用异常,并使我更灵活地控制RAM使用情况。此外,反复尝试加载文件、捕获异常并尝试加载较小文件(需要递归)似乎很困难和昂贵。 - Tiago.SR
关于内存映射:我之前研究了一下,觉得处理起来很无聊。需要使用C的open()和各种琐碎的东西,还得放弃std::fstream。看起来最好的部分读取文件的方法是派生出std::streambuf并实现一个"范围缓冲区",唉。而std::vector<unsigned char>就简单多了... - Tiago.SR
@Tiago.SR 我已经扩展了我的回答来回答你的新问题。 - eerorika
我再次阅读了您的答案,现在看起来最好使用mmap自始至终,除了这里所说的:http://dannythorpe.com/2004/03/19/the-hidden-costs-of-memory-mapped-files/ 页面错误,需要连续的地址空间块(vector也有同样的问题,但与deque混合使用可以缓解),无法控制文件在RAM中停留的时间... - Tiago.SR
1
内存分配仅在虚拟内存耗尽时失败,而不是 RAM。 - Martin Bonner supports Monica
显示剩余2条评论

2
作为对@user2097303的回答的补充,我想要补充一点,即vector保证连续分配。对于长时间运行的应用程序,这将导致内存碎片化,最终不再存在连续的内存块,虽然在块之间有大量的空间可用。

因此,将数据存储到deque中可能是一个好主意。

“deque” 会不会因为需要访问 RAM 的不同部分来获取完整文件而变慢?也许我可以使用 “vector” 将初始文件加载到 RAM 中,然后如果需要替换或添加新文件,则使用 “deque” …你觉得呢? - Tiago.SR

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接