从内存向文件写入大量数据的最快方法是什么?

7
我有一个程序会生成大量数据,并将其放入队列中进行写入。但问题在于生成数据的速度比我当前的写入速度要快,这导致了内存占用最大并开始减缓。顺序无关紧要,因为我计划稍后解析文件。
我搜索了一下,发现了一些问题,这些问题帮助我设计了我的当前进程(但我仍然觉得它很慢)。以下是我的目前代码:
//...background multi-threaded process keeps building the queue..
FileWriter writer = new FileWriter("foo.txt",true);
        BufferedWriter bufferWritter = new BufferedWriter(writer);
        while(!queue_of_stuff_to_write.isEmpty()) {
            String data = solutions.poll().data;
            bufferWritter.newLine();
            bufferWritter.write(data);
        }
        bufferWritter.close();

我在编程方面还是新手,所以可能会评估错误(也许是硬件问题,因为我正在使用EC2),但是否有一种非常快速的方法可以将队列结果转储到文件中?如果我的方法可行,我能否通过某种方式改进它?由于顺序无关紧要,是否更有意义将其写入多个驱动器上的多个文件?线程会使它更快吗?等等...我不确定最佳方法是什么,任何建议都将非常好。我的目标是保存队列的结果(很抱歉不能输出到/dev/null :-)),并尽可能保持应用程序的内存消耗量低(我不确定,但队列填充了15Gig,所以我假设它将是一个15Gig+文件)。
参考以下链接: Java中写入大量数据到文本文件的最快方法 (意识到我应该使用缓冲写入器) Java在Windows上并发写入文件 (让我认识到多线程写入可能不是一个好主意)

我理解 CPU 速度 > 硬盘速度,因此写入很可能永远输给处理,我只是在努力思考如何帮助硬盘速度更接近处理速度。 - Lostsoul
很多事情取决于你的瓶颈在哪里。我怀疑如果你最大化了磁盘IO的带宽(这似乎是你的问题),你也可以在账户成本方面达到最大值。我同意多线程写入不会有太大帮助。 - Peter Lawrey
一个粗略的估算是,每15 GB的费用是$4。 - Peter Lawrey
1
@PeterLawrey 临时存储没有额外费用(它已包含在实例中,但不是持久的),我可用的容量略低于1TB。 - Lostsoul
如果您不关心成本,我建议首先尝试使用最快的方式写入文件,例如使用命令行中的dd。或者您可以使用NIO以大块大小(例如32-256KB)进行操作。 - Peter Lawrey
4个回答

2
看着那段代码,一个引人注意的地方是字符编码。你正在编写字符串,但最终将字节发送到流中。编写器在后台进行字符到字节的编码,并且它在处理写入的线程中执行。这可能意味着花费了一些时间进行编码,从而延迟了写入操作,这可能会降低数据写入的速率。
一个简单的改变是使用 byte[] 的队列代替 String,在线程推送到队列时进行编码,并使用 BufferedOutputStream 而不是 BufferedWriter 进行IO操作。
如果编码文本平均每个字符少于两个字节,则这也可能减少内存消耗。对于拉丁文和UTF-8编码,这通常是正确的。
然而,我怀疑很可能你只是比IO子系统能够处理更快地生成数据。你需要使IO子系统更快 - 通过使用更快的IO子系统(如果您使用的是EC2,也许租用一个更快的实例,或者编写到不同的后端 - SQS vs EBS vs 本地磁盘等),或通过某种方式并联多个IO子系统。

1

是的,将多个文件写入多个驱动器应该会有所帮助,如果没有其他东西同时写入这些驱动器,性能应该会随着驱动器数量的增加而线性扩展,直到I/O不再成为瓶颈。您还可以尝试一些其他优化措施来进一步提高性能。

如果您正在生成大型文件并且磁盘无法跟上,您可以使用GZIPOutputStream来缩小输出大小--这反过来会减少磁盘I/O的数量。对于非随机文本,通常可以期望至少2倍至10倍的压缩比。

    //...background multi-threaded process keeps building the queue..
    OutputStream out = new FileOutputStream("foo.txt",true);
    OutputStreamWriter writer = new OutputStreamWriter(new GZIPOutputStream(out));
    BufferedWriter bufferWriter = new BufferedWriter(writer);
    while(!queue_of_stuff_to_write.isEmpty()) {
        String data = solutions.poll().data;
        bufferWriter.newLine();
        bufferWriter.write(data);
    }
    bufferWriter.close();

如果你要输出常规(即重复的)数据,你可能还想考虑切换到其他输出格式——例如,数据的二进制编码。根据你的数据结构,将其存储在数据库中可能更有效。如果你要输出XML并真正想坚持使用XML,你应该研究二进制XML格式,例如EXI或Fast InfoSet。


0

我猜只要你的数据是通过计算生成而不是从另一个数据源加载,写入数据始终会比生成数据慢。

你可以尝试将数据写入多个文件(不要写在同一个文件中,因为会出现同步问题),或者使用多个线程(但我猜这并不能解决你的问题)。

你是否可以等待应用程序的写入部分完成操作,然后继续进行计算?

另一种方法是: 你清空了队列吗?solutions.poll() 是否减少了 solutions 队列的数量?


0

使用多个线程写入不同的文件是一个好主意。此外,您应该研究一下设置BufferedWriter缓冲区大小的方法,这可以在构造函数中完成。尝试使用10 Mb缓冲区进行初始化,看看是否有帮助。


这是真的吗?同时向同一机械硬盘写入两个文件将比先写一个再写另一个要花费更长的时间。 - Roman Starkov

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