Java文件I/O吞吐量下降

3
我有一个程序,其中每个线程从文件中一次性读入很多行数据,处理这些行,并将它们写入另一个文件。四个线程将要处理的文件列表分别拆分成四份。我在两种情况下遇到了奇怪的性能问题:
- 四个文件,每个文件有50,000行
- 吞吐量从每秒处理700行开始下降到大约100行/秒
- 30,000个文件,每个文件有12行
- 吞吐量从每秒处理800行开始保持稳定
这是我正在开发的内部软件,所以不能分享任何源代码,但程序的主要步骤如下:
1. 将文件列表分为四个工作线程的任务清单。 2. 启动所有线程。 3. 线程一次性读取最多100行,存储在`String[]`数组中。 4. 线程对数组中的所有行进行转换。 5. 线程将行写入一个文件(与输入文件不同)。 6. 步骤3至5重复,直到所有文件都被处理完毕。
我不理解的是,为什么30k个每个文件只有12行的文件可以比几个有很多行的文件更高效。我本来期望文件的打开和关闭开销比读取单个文件的开销更大。此外,在前一种情况下性能的下降呈指数增长。
我将最大堆大小设置为1024 MB,它似乎最多只使用了100 MB,所以过度的垃圾回收不是问题。你有其他的想法吗?
6个回答

3

根据您提供的数据,我猜测GC可能不是问题所在。我怀疑这是磁盘的正常行为,受到许多并发线程的操作。当文件很大时,磁盘必须在线程之间多次切换上下文(产生显着的磁盘寻道时间),而开销是明显的。对于小文件,它们可能作为单个块读取,没有额外的寻道时间,因此线程之间的干扰不会太大。

在使用单个标准磁盘时,串行IO通常比并行IO更好。


我将尝试重新编写代码,使得主线程一次性读取多行数据,允许多个工作线程进行处理,然后主线程再次输出结果。谢谢! - A B

2
我假设文件存储在同一个硬盘上,这种情况下你可能会在多线程并发读写时使硬盘过度使用(或者清空硬盘\操作系统缓存)。更好的方式是创建一个专用的读写线程来处理IO,然后修改你的模式,使转换(听起来很耗费资源)由多个线程处理。你的IO线程可以随着结果的出现获取和重叠写入与转换操作。这样应该能阻止硬盘过度使用,并平衡你的IO和CPU的负载。

1

你尝试过运行Java分析器吗?它会指出代码中哪些部分运行最慢。从这个讨论中可以看出,Netbeans分析器是一个不错的选择。


我使用Eclipse的MAT插件查看了堆转储,但并没有什么特别有用的信息。在第一种情况下,它只告诉我有很多字符串被存储,而这点我是知道的。接下来我会试着看看Netbeans的。 - A B
我并不是马上对堆上存储的内容感兴趣。相反,我想知道在两种情况下哪些语句需要最长时间才能完成。这至少可以告诉你是内存压力(创建字符串需要很长时间)还是文件I/O(读取需要很长时间)、文件访问(打开需要很长时间)或完全其他的问题! - Karmastan

1

很可能你的线程持有缓冲的String[]太久了。即使你的堆比你需要的大得多,由于垃圾回收,吞吐量可能会受到影响。看看你持有这些引用的时间有多长。

你也可能在等待虚拟机分配更多的内存- 请求Xmx1024m并不会立即分配那么多内存,它会在需要更多内存时获取所需的内存。你也可以尝试使用-Xms1024m -Xmx1024m(即在开始时分配所有内存)来测试是否是这种情况。


我已经启用了两个选项。同一个数组一直在被重复使用,只是每次读取一行时都会分配新的字符串,所以我认为无论被覆盖的引用是什么,垃圾收集器都可以立即进行回收。当写出这些引用时,我是否应该显式地将它们设置为null? - A B

0

你的线程可能存在停止和锁定的情况(其中一个线程将100行读入内存并在完成处理之前保持锁定,而不是在从文件中读取完毕后放弃它)。我不是Java线程方面的专家,但这是值得考虑的。


嗯,每个线程都有自己的读取器和写入器,而且没有两个线程会同时操作同一个文件。但是仍然可能存在锁定问题吗? - A B
我的猜测是,如果线程之间没有共享,那么就不会有锁定问题。我认为你选择的答案最好。 - Eric Andres

0

我会审查这个过程。如果您使用 BufferedReader 和 BufferedWriter,每次读取和处理 100 行没有任何优势。这只是增加了复杂性和潜在错误的另一个来源。一次只做一件事,并简化您的生活。


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