【问题标题】:Java 7's nio.file package is uber slow at creating new filesJava 7 的 nio.file 包在创建新文件时非常慢
【发布时间】:2013-03-04 06:01:45
【问题描述】:

我正在尝试从 java 程序创建 300M 文件,我从旧的文件 API 切换到新的 java 7 nio 包,但新包的运行速度比旧包还要慢。

我发现 CPU 使用率比使用旧文件 API 时要低,但我正在运行这个简单的代码,我获得了 0.5Mbytes/sec 的文件传输速率,并且来自 java 的写入正在读取一个磁盘并写入另一个(写入是访问磁盘的唯一进程)。

Files.write(FileSystems.getDefault().getPath(filePath), fiveToTenKBytes, StandardOpenOption.CREATE);

这里有没有希望获得合理的吞吐量?


更新:

我正在从大文件中解压缩 3 亿个 5-10k 字节的图像文件。我有 3 个磁盘、1 个本地磁盘和 2 个附加 SAN(大文件的典型吞吐率约为 20MB/秒)。

我还尝试了此代码,该代码将速度提高到几乎不低于 2MB/秒的吞吐量(解压这些文件需要 9 天)。

ByteBuffer byteBuffer = ByteBuffer.wrap(imageBinary, 0, (BytesWritable)value).getLength());
FileOutputStream fos = new FileOutputStream( imageFile );
fos.getChannel().write(byteBuffer);
fos.close();

我从本地磁盘读取并写入 SAN 附加磁盘。我正在读取 Hadoop SequenceFile 格式,hadoop 通常能够使用基本相同的代码以 20MB/秒的速度读取这些文件。

除了超级慢之外,唯一显得格格不入的是,我看到的读取 IO 比写入 IO 多大约 2:1,尽管序列文件是 gzip 压缩的(尽管图像实际上是 1:1 的比率),所以压缩文件应该是大约。 1:1 输出。


第二次更新

查看iostat,我看到一些奇数,我们在这里查看xvdf,我有一个java进程从xvdb读取并写入xvdfxvdf上没有其他进程处于活动状态

iostat -d 30
Device:            tps    kB_read/s    kB_wrtn/s    kB_read    kB_wrtn
xvdap1            1.37         5.60         4.13        168        124
xvdb             14.80       620.00         0.00      18600          0
xvdap3            0.00         0.00         0.00          0          0
xvdf            668.50      2638.40       282.27      79152       8468
xvdg           1052.70      3751.87      2315.47     112556      69464

xvdf 上的读取量是写入量的 10 倍,这令人难以置信。

fstab
/dev/xvdf       /mnt/ebs1       auto    defaults,noatime,nodiratime     0       0
/dev/xvdg       /mnt/ebs2       auto    defaults,noatime,nodiratime     0       0

【问题讨论】:

  • 这些文件有多大?
  • @parsifal "我正在尝试创建 3 亿个文件 [...]"
  • 我将其读作“我正在尝试创建 3 亿个(或数千个)文件”,而不是“我正在尝试创建一个 300 Mb 大小的文件”(否则,为什么要使用“ M”而不是“Mb”?)。
  • 第二个问题:这些磁盘是本地连接的还是通过网络访问的?
  • 3 亿个 5-10k 字节的图像文件。在 AWS 上从本地磁盘上的 12GB 大文件解压到 SAN 附加磁盘,两者的典型大文件吞吐率约为 20MB/秒。

标签: java file-io java-7 nio2


【解决方案1】:

我认为您的缓慢来自创建新文件,而不是实际传输。我相信在 Linux 中创建文件是一个同步操作:系统调用在创建文件和更新目录之前不会返回。这表明您可以做几件事:

  • 将多个写入线程与单个读取线程一起使用。读取器线程将从源文件中读取数据到byte[],然后创建一个Runnable,从这个数组中写入输出文件。使用带有大量线程的threadpool——可能有100个或更多——因为他们将花费大部分时间等待creat完成。根据您拥有的内存量设置此池的入站队列的容量:如果您的文件大小为 10k,那么 1,000 的队列容量似乎是合理的(没有充分的理由让读者远远领先于作者,因此您甚至可以使用两倍线程数的容量)。
  • 使用基本的BufferedInputStreams 和BufferedOutputStreams 而不是NIO。您的问题是系统调用,而不是内存速度(NIO 类旨在防止堆内存和堆外内存之间的复制)。

我将假设您已经知道不要尝试将所有文​​件存储到单个目录中。甚至可以在一个目录中存储数百个文件。

作为另一种选择,您是否考虑过使用 S3 进行存储?我猜它的存储桶键比实际目录效率高得多,并且有一个filesystem 可以让您像访问文件一样访问存储桶(自己没有尝试过)。

【讨论】:

  • 我确实创建了 2 个进程这样做,磁盘速度急剧下降,但 2 个进程的总和是 2MB/秒,好一点,但看起来更多的异步进程不会帮助情况。至于S3,这是我的第一个想法,它以巨大的爆炸失败了。在线 2 周,他们的技术人员试图上传 300M 文件失败并花费了我 10k 的使用费,即使它第一次工作(肯定不会)你说 3k 只是为了上传文件。看看那些 0.10 美元 / 100 美元的小费,它很快就会让你毛骨悚然!
  • 我现在正在尝试大文件(我可以非常快速地创建它),并在大文件中存储一个指向字节的指针。到目前为止,这一切都进行得更加顺利,这是我阅读时 facebook 使用的方法。完成后我会发布它的成功。
  • 最终结果:不要做300M的小文件。我们正在转向一个更复杂的系统,在该系统中,我们将二进制数据加载到大文件中,并为二进制数据保留一个索引偏移量。我们还在尝试使用大型 mysql/myisam 表作为一个不错的选择。
【解决方案2】:

如果我正确理解了您的代码,那么您将 300M 文件分成小块(“fiveToTenKBytes”)。

考虑使用a Stream approach

如果您正在写入磁盘,请考虑使用 BufferedOutputStream 包装 OutputStream。

例如类似:

try (BufferedOutputStream bos = new BufferedOutputStream(Files.newOutputStream(Paths.getPath(filePathString), StandardOpenOption.CREATE))){

 ...

}

【讨论】:

  • @JoachimSauer 感谢您的编辑,但 StackOverflow 的方法链接存在问题...
  • 我知道,但我添加的链接运行良好(至少对我而言)。现在站着的那个只会带你到Files 文档,因为里面有空间。
  • 查看问题中的更新以获得答案,我相信我使用的是缓冲方法。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-11-17
  • 1970-01-01
  • 2016-05-21
  • 1970-01-01
相关资源
最近更新 更多