如何让Java在使用GZIPInputStream时利用我的多核处理器?

8

我在我的程序中使用了GZIPInputStream,我知道如果我能让Java在多个核心上运行我的程序,性能会得到改善。

一般来说,标准VM有没有命令行选项可以在多个核心上运行?它现在只在一个核心上运行。

谢谢!

编辑

我在Windows XP上运行普通的Java SE 6更新17。

把GZIPInputStream放在单独的线程上是否会有帮助?不!不要把GZIPInputStream放在单独的线程上!不要多线程I/O!

编辑2

虽然我正在读写同一个磁盘,但总体而言,I/O是瓶颈...

一般来说,有没有一种方法可以使GZIPInputStream更快?或者有一个可以并行运行的GZIPInputStream替代品?

编辑3 我使用的代码片段:

GZIPInputStream gzip = new GZIPInputStream(new FileInputStream(INPUT_FILENAME));
DataInputStream in = new DataInputStream(new BufferedInputStream(gzip));

1
你使用的平台和虚拟机版本是什么? - moritz
2
当使用线程时,这不会自动发生吗? - Jay
1
相关文章 http://www.codinghorror.com/blog/archives/001231.html - Graphics Noob
你必须学会如何创建多线程程序,其中每个线程从共同的工作负载队列中执行许多小任务。 - Thorbjørn Ravn Andersen
我完全相信将GZIPInputStream放在单独的线程上并使用多个线程处理多个独立流是完全安全的。收益取决于访问的独立程度,但你真的不必担心一次只能在一个线程上打开一个GZIPInputStream。否决。 - Audrius Meškauskas
9个回答

16

据我所知,从该流中读取数据的操作是单线程的,因此如果您只读取一个文件,则多个 CPU 并不能帮助您。

但是,您可以拥有多个线程,每个线程解压缩一个不同的文件。

话虽如此,解压缩这些日子并不特别需要计算强度,您更有可能受到 I/O 成本的阻塞(例如,如果您正在读取硬盘上两个不同区域的非常大的文件)。

更一般地说(假设这是针对初学 Java 的人的问题),Java 不会自动并行执行任务。您必须使用线程告诉它要做哪些工作单元以及如何在它们之间进行同步。Java(在操作系统的帮助下)通常会利用所有可用的核心,并且如果存在比核心更多的线程(通常情况下是这种情况),它还会在同一核心上交换线程。


2
+1 注意IO瓶颈。在这些情况下,这太容易被忽视了。 - BalusC
1
不要不要不要使用多线程进行I/O操作!操作系统已经在多个应用程序之间同步了IO,如果在提供的IO抽象层之上添加额外的线程,特别是在读取时,如果您为其使用多个线程,则会导致整个计算机崩溃。 - Esko
我的个人经验是,它不会损坏计算机,只是你看不到机器翻译的实际好处。 - Uri
多线程IO通常会导致大量的硬盘寻址,尽管它们可能不会导致计算机死机,但肯定会使其进入昏迷状态一段时间。 - Michael Borgwardt

6

PIGZ是GZip的并行实现,完全可以替代gzip,在压缩数据时充分利用多个处理器和多核心。 http://www.zlib.net/pigz/ 它还没有Java版本,有人愿意开发吗?当然,世界需要它的Java版本。

有时压缩或解压缩会消耗大量的CPU资源,但这有助于避免I/O成为瓶颈。

请参阅惠普实验室的Dataseries(C++)。PIGZ仅并行化压缩,而Dataseries将输出分成大的压缩块,这些块可以并行解压缩。此外还有许多其他功能。


看起来现在有一个Java实现的PIGZ:https://github.com/shevek/parallelgzip - FrustratedWithFormsDesigner

2

将您的GZIP流包装在缓冲流中,这样可以显著提高性能。

OutputStream out = new BufferedOutputStream(
    new GZIPOutputStream(
        new FileOutputStream(myFile)
    )
)

同样适用于输入流。使用缓冲的输入/输出流可以减少磁盘读取次数。


我应该将GZIP流包装在缓冲流中,还是将缓冲流包装在GZIP流中?例如:new GZIPInputStream(new BufferedInputStream(...)) new GZIPOutputStream(new BufferedOutputStream(...)) 与 new BufferedInputStream(new GZIPInputStream(...)) new BufferedOutputStream(new GZIPOutputStream(...)) - Rudiger
1
我一直认为GZIPOutputStream已经被缓冲了。 - Chii
它有一个flush()方法,这暗示它是带缓冲的。此外,还有一个构造函数可以让你指定缓冲区大小。对我来说足够有信服力了 :) - Carl Smotricz
@Rudiger,你能发布一下你正在使用的流的代码片段吗?你是在使用ObjectOutputStream吗? - Sam Barnum
2
也许这只是GZIPOutputStream(512)的默认缓冲区大小与BufferedOutputStream(8192)的默认缓冲区大小之间的差异。如果您将缓冲流删除并将缓冲区大小增加到8192,我很想知道是否能获得好的结果。 - Sam Barnum
显示剩余3条评论

2
我没有看到任何回答涉及程序的其他处理。
如果你只是解压文件,最好使用命令行工具gunzip;但可能有一些处理正在处理从该流中提取出来的文件。
如果你正在提取以合理大小块出现的东西,则应将这些块的处理与解压缩分开进行。
你可以在每个大字符串或其他数据块上手动启动一个线程;但自Java 1.6以来,你最好使用java.util.concurrent中的一种新型类,例如ThreadPoolExecutor。
更新
从问题和其他评论中,我不清楚你是否真的只是使用Java提取文件。如果你真的认为你应该与gunzip竞争,那么你可以通过使用大缓冲区来获得一些性能;即使用10 MB(二进制,而不是十进制!-1048576)的缓冲区,在单个饮料中填充它,并以同样的方式写入磁盘。这将给你的操作系统一个机会进行一些中等规模的磁盘空间规划,你也需要更少的系统级调用。

我不仅仅是使用Java提取文件,但我意识到我的问题有点含糊不清。 - Rudiger

0

压缩似乎是并行化的难点,因为压缩器发出的字节是输入的前W个字节的非平凡函数,其中W是窗口大小。显然,您可以将文件分成多个部分,并为每个部分创建独立的压缩流,在其自己的线程中运行。您可能需要保留一些压缩元数据,以便解压缩器知道如何将文件重新组合。


1
实际上,压缩在很大程度上受到并行处理的帮助。 - Rudiger
并行处理是否有助于压缩或解压缩取决于压缩函数。 - Chip Uni

0

使用gzip进行压缩和解压缩是一个串行的过程。如果要使用多个线程,您需要编写一个自定义程序将输入文件分成多个流,然后再编写一个自定义程序将它们解压缩并重新组合在一起。无论哪种方式,IO都会成为瓶颈,而CPU使用率则远远不及其重要。


也许有人可以编写一个遵循与InputStream和OutputStream相同API的压缩输入/输出流,但适用于多核时代。然而,I/O是瓶颈。 - Rudiger
它不再是gzip格式,而是一种自定义格式。 - user177800

0

运行多个虚拟机。每个虚拟机都是一个进程,您应该能够在每个核心上运行至少三个进程而不会降低性能。当然,您的应用程序必须能够利用多处理才能受益。没有什么万能的方法,这就是为什么你会看到新闻报道抱怨我们还不知道如何使用多核机器。

然而,有很多人将他们的应用程序结构化为一个主控程序,管理一组工作进程并将工作包分配给它们。并非所有问题都适合以这种方式解决。


0

标准的GZipInputStream无法并行化,因为它是单线程的。但是你可以将解压缩和处理解压后的流进行管道化,分配到不同的线程中去处理。也就是说,将GZipInputStream设置为生产者,将任何处理它的过程设置为消费者,并使用有界阻塞队列将它们连接起来。


0

我认为假设多线程IO 总是 不好的是一个错误。你可能需要对你的特定情况进行分析,因为:

  • 最近的操作系统使用当前空闲内存作为缓存,当你读取文件时,你的文件实际上可能并不在硬盘上。
  • 最近的硬盘(如SSD)具有更快的访问时间,因此更改读取位置不再是一个问题。
  • 这个问题太笼统了,不能假设我们从单个硬盘中读取。

您可能需要调整读取缓冲区大小,使其足够大以减少切换成本。在边界情况下,可以将所有文件都读入内存并在那里并行解压缩 - 更快且没有任何IO多线程损失。但是,一些不那么极端的方法可能效果更好。

您也不需要做任何特殊的事情来使用JRE上的多个可用核心。不同的线程通常会由操作系统管理,使用不同的核心。


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