使用C ++读取大(~1GB)数据文件有时会抛出bad_alloc,即使我有超过10GB的RAM可用

时间:2016-02-06 11:40:39

标签: c++ file-io bad-alloc

我正在尝试读取大小约为1.1GB的.dat文件中包含的数据。 因为我是在一台16GB的RAM机器上这样做的,所以尽管将整个文件一次性读入内存并且只是在处理之后才会出现问题。

为此,我使用了this SO answer中的slurp函数。 问题是代码有时(但不总是)抛出bad_alloc异常。 看看任务管理器,我发现总有至少10GB的可用内存可用,所以我看不出内存是怎么回事。

以下是重现此错误的代码

#include <iostream>
#include <fstream>
#include <sstream>
#include <string>

using namespace std;

int main()
{
    ifstream file;
    file.open("big_file.dat");
    if(!file.is_open())
        cerr << "The file was not found\n";

    stringstream sstr;
    sstr << file.rdbuf();
    string text = sstr.str();

    cout << "Successfully read file!\n";
    return 0;
}

可能导致此问题的原因是什么? 什么是避免它的最佳做法?

3 个答案:

答案 0 :(得分:4)

您的系统具有16GB的事实并不意味着任何时候任何程序都可以分配给定数量的内存。实际上,这可能适用于只有512MB物理RAM的机器,如果可以使用交换,或者在具有128GB RAM的HPC节点上可能会失败 - 它完全由您的操作系统决定如何你可以在这里找到很多记忆。

我还认为std::string 永远从不选择的数据类型,如果实际处理文件,可能是二进制,那么大。

这里的要点是绝对不知道stringstream试图分配多少内存。每当分配的内部缓冲区变得太小而无法包含传入字节时,一个非常合理的算法会使分配的内存量翻倍。此外,libc ++ / libc可能也有自己的分配器,这里会有一些分配开销。

请注意,stringstream::str()会返回stringstream内部状态中包含的数据的副本,再次使用至少2.2 GB的堆完成这项任务。

实际上,如果您需要将大型二进制文件中的数据作为可以使用索引运算符[]访问的内容来处理,请查看内存映射您的文件;这样,你得到一个指向文件开头的指针,并且可以使用它,就好像它是内存中的普通数组一样,让你的操作系统负责处理底层内存/缓冲区管理。这是操作系统的用途!

如果您以前不知道Boost,那就是C ++的扩展标准库&#34;&#34;到目前为止,它有一个抽象内存映射文件的类:mapped_file

  

我正在阅读的文件包含ASCII表格形式的一系列数据,即float1,float2\nfloat3,float4\n....

     

我浏览SO上提出的各种可能的解决方案来解决这类问题,但我对这个(对我来说)奇怪的行为感到疑惑。在这种情况下你会推荐什么?

取决于;我实际上认为处理这个问题的最快方法(因为文件IO比ASCII的内存中解析慢很多)是逐步地将文件解析为float变量的内存中数组;可能利用您的操作系统预先获取SMP功能,因为如果您为文件读取和浮动转换生成单独的线程,您甚至无法获得那么大的速度优势。用于从std::copy读取到std::ifstream的{​​{1}}应该可以正常使用。

  

我还没有得到一些东西:你说文件IO比内存中解析要慢得多,而且我理解这一点(这也是我想立刻读取整个文件的原因)。然后你说最好的方法是将整个文件递增地解析为内存中的float数组。你到底是什么意思?这是不是意味着逐行读取文件,导致大量的文件IO操作?

是的,不是:首先,当然,如果您刚订购了整个文件,那么您将拥有更多上下文切换。但那些 读取,因此如果您不使用极度随机的std::vector<float>长度,将立即获取大量文件。此外,您不能推断尝试分配至少1.1GB大小的RAM块的代价 - 这要求进行一些严格的页表查找,这些查找速度也不快。

现在,我们的想法是偶尔进行上下文切换以及如果您保持单线程状态,有时候您不会读取文件,因为您仍然处于静止状态忙于将文本转换为浮动仍然意味着性能降低,因为大多数情况下,您的read几乎会立即返回,因为您的操作系统/运行时已经预取了文件的重要部分。

一般来说,对我来说,你似乎担心所有错误的事情:性能对你来说似乎很重要(这真的 重要吗?在这里?你正在使用用于交换浮动的脑死文件格式,它既臃肿,丢失信息,而且解析起来很慢),但你首先要先读取整个文件,然后开始将其转换为数字。坦率地说,如果性能对您的应用程序有任何重要性,您将开始对其进行多线程/处理,以便在仍然读取数据时可能已经发生了字符串解析。使用几千兆字节到几千兆字节的缓冲区读取到read边界,并与创建浮点内存表的线程进行交换,听起来基本上会将读取+解析时间减少到读取+非 - 可以在不牺牲读取性能的情况下进行测量,并且无需使用千兆字节的RAM来解析顺序文件。

顺便说一句,给你一个关于ASCII存储浮点数有多糟糕的印象:

典型的32位单精度IEEE753浮点数具有大约6-9个有效十进制数字。因此,您需要至少6个字符才能用ASCII表示这些字符,一个\n,通常一个指数分频器,例如: .,平均2.5位十进制指数,加上平均半个符号字符(E或不符号),如果您的数字是从所有可能的IEEE754 32位浮点数中统一选择的:

-

平均为11个字符。

在每个号码后添加一个-1.23456E-10 ,

现在,你的角色是1B,这意味着你将你的4B实际数据炸掉3倍,仍然失去精确度。

现在,人们总是来告诉我明文更有用,因为如果有疑问,用户可以阅读它...我还没有看到一个用户可以浏览1.1GB(根据我上面的计算)那个大约9000万个浮点数,或者4500万个浮点数)并且不会疯狂。

答案 1 :(得分:2)

在32位可执行文件中,总内存地址空间为4GB。其中,有时1-2 gb保留供系统使用。

要分配1 GB,您需要1 GB的连续空间。要复制它,您需要2个1 GB的块。这很容易失败,不可预测。

有两种方法。首先,切换到64位可执行文件。这不会在32位系统上运行。

其次,停止分配1 GB连续块。一旦你开始处理那么多数据,对它进行分段和/或流式传输就会开始变得很有意义。做得对,你也可以在读完之前开始处理它。

有许多文件io数据结构,从stxxl到boost,或者你可以自己动手。

答案 2 :(得分:1)

堆的大小(用于动态分配的内存池)独立于机器的RAM量而受到限制。您应该使用其他一些内存分配技术进行如此大的分配,这可能会迫使您改变从文件中读取的方式。

如果您在基于UNIX的系统上运行,则可以在Windows平台上运行时检查函数vmalloc或VirtualAlloc函数。