Java多线程文件下载性能
Posted
技术标签:
【中文标题】Java多线程文件下载性能【英文标题】:Java multithreaded file downloading performance 【发布时间】:2011-03-25 11:43:29 【问题描述】:最近参与了一个需要比以往更多的 IO 交互的项目,我觉得我想超越常规库(尤其是 Commons IO)并解决一些更深入的 IO 问题。
作为一项学术测试,我决定实现一个基本的多线程 HTTP 下载器。这个想法很简单:提供一个 URL 来下载,代码将下载文件。为了提高下载速度,文件被分块并同时下载每个块(使用 HTTP Range: bytes=x-x
header)以使用尽可能多的带宽。
我有一个工作原型,但正如您可能已经猜到的那样,它并不完全理想。目前我手动启动 3 个“下载器”线程,每个线程下载文件的 1/3。这些线程使用一个通用的、同步的“文件写入器”实例将文件实际写入磁盘。当所有线程都完成后,“文件写入器”就完成了,所有打开的流都被关闭了。一些sn-ps的代码给你一个想法:
线程启动:
ExecutorService downloadExecutor = Executors.newFixedThreadPool(3);
...
downloadExecutor.execute(new Downloader(fileWriter, download, start1, end1));
downloadExecutor.execute(new Downloader(fileWriter, download, start2, end2));
downloadExecutor.execute(new Downloader(fileWriter, download, start3, end3));
每个“下载器”线程下载一个块(缓冲)并使用“文件写入器”写入磁盘:
int bytesRead = 0;
byte[] buffer = new byte[1024*1024];
InputStream inStream = entity.getContent();
long seekOffset = chunkStart;
while ((bytesRead = inStream.read(buffer)) != -1)
fileWriter.write(buffer, bytesRead, seekOffset);
seekOffset += bytesRead;
“文件写入器”使用RandomAccessFile
到seek()
和write()
将块写入磁盘:
public synchronized void write(byte[] bytes, int len, long start) throws IOException
output.seek(start);
output.write(bytes, 0, len);
考虑到所有因素,这种方法似乎有效。但是,它的效果不是很好。对于以下几点,我将不胜感激一些建议/帮助/意见。非常感谢。
-
此代码的 CPU 使用率 已经达到了顶峰。它使用了我一半的 CPU(两个内核各占 50%)来执行此操作,这比几乎不会对 CPU 造成压力的同类下载工具成倍增加。我对这个 CPU 使用率的来源有点迷惑,因为我没想到会这样。
通常,3 个线程中似乎有 1 个线程明显落后。其他 2 个线程将完成,之后第三个线程(似乎主要是第一个线程的第一个线程)需要 30 秒或更长时间才能完成。我可以从任务管理器中看到 javaw 进程仍在进行小 IO 写入,但我真的不知道为什么会发生这种情况(我猜是竞态条件?)。
尽管我选择了相当大的缓冲区 (1MB),但我感觉
InputStream
几乎从未真正填满缓冲区,这导致 IO 写入超出我的预期。我的印象是,在这种情况下,最好将 IO 访问保持在最低限度,但我不确定这是否是最佳方法。
我意识到 Java 可能不是执行此类操作的理想语言,但我确信与我当前的实现相比,它的性能要高得多。在这种情况下,蔚来汽车值得探索吗?
注意:我使用 Apache HTTPClient 进行 HTTP 交互,这就是 entity.getContent()
的来源(以防有人想知道)。
【问题讨论】:
在这里找到了一个很好的相关主题:***.com/questions/921262/… 今晚回家后可能会尝试一下:) 更新:高 CPU 使用率是由于 ExecutorService isTerminated() 方法调用上的 while() 循环造成的。哇! 我认为很大程度上取决于网络配置以及网络接口卡(物理)。即使您有多个线程在下载同一个文件,但负责序列化字节的 NIC 可能会成为瓶颈! 【参考方案1】:回答我自己的问题:
-
CPU 使用率增加是由于
while()
循环正在等待线程完成。事实证明,awaitTermination
是等待Executor
完成的更好选择:)
(以及 3 和 4)这似乎是野兽的本性;最后,我通过仔细同步不同线程来实现我想做的事情,每个线程都下载一个数据块(嗯,特别是这些块写回磁盘)。
【讨论】:
【参考方案2】:大概 Apache HTTP 客户端将使用较小的缓冲区进行一些缓冲。它将需要一个缓冲区来合理地读取 HTTP 标头,并可能处理分块编码。
【讨论】:
【参考方案3】:我在 Windows 上获得最佳性能的直接想法是使用 IO completions ports。我不知道的是(a)其他操作系统中是否有类似的概念,以及(b)是否有合适的 Java 包装器?不过,如果可移植性对您来说并不重要,那么您可以使用 JNI 推出自己的包装器。
【讨论】:
【参考方案4】:设置一个非常大的套接字接收缓冲区。但实际上,您的性能将受到网络带宽的限制,而不是 CPU 带宽。您实际上所做的只是将 1/3 的网络带宽分配给每个下载器。如果你得到很多好处,我会感到惊讶。
【讨论】:
三个连接可能比一个快,在传输开始时短暂。 TCP 需要花一点时间才能找到最佳窗口大小,所以如果使用并行连接,这个过程会快 3 倍! 这就是我首先进行分块的原因,将文件分成 3 个块可以让我下载同一个文件的速度快 3 倍,假设单个块太慢而无法使连接饱和。但是下载速度已经达到最大值,所以这不是问题。我担心的是CPU使用率。会不会与代码在 Eclipse 中执行有关? 好吧,您正在执行 N-1 次冗余搜索,而我上次查看它时,那是几十年前,搜索是一项非常昂贵的操作。每个作家只需要寻找一次;之后它只是顺序 I/O。以上是关于Java多线程文件下载性能的主要内容,如果未能解决你的问题,请参考以下文章