2010-08-04 119 views
18

最近在一个需要更多IO交互的项目上工作比我习惯的要多,我觉得我想看看常规库(特别是Commons IO),并且更多地处理深入的IO问题。Java多线程文件下载性能

作为一项学术测试,我决定实施一个基本的多线程HTTP下载程序。这个想法很简单:提供一个URL下载,代码将下载该文件。为了提高下载速度,文件被分块并且每个块同时下载(使用HTTP头)以尽可能多地使用带宽。

我有一个工作原型,但正如您可能已经猜到的那样,它并不完全理想。目前我手动启动3个“下载器”线程,每个线程下载1/3的文件。这些线程使用通用的,同步的“文件写入器”实例将文件实际写入磁盘。当所有线程完成时,“文件写入器”完成并且任何打开的流都关闭。一些代码片段来给你一个想法:

线程启动:

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)); 

每一个“下载”线程下载一个块(缓冲),并使用“文件写入”写入磁盘:

public synchronized void write(byte[] bytes, int len, long start) throws IOException 
{ 
     output.seek(start); 
     output.write(bytes, 0, len); 
} 

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; 
} 

“文件作家” 使用RandomAccessFileseek()write()的块磁盘写入磁盘

所有考虑事项,这种方法似乎工作。但是,它不能很好地工作。我希望对以下几点提供一些建议/帮助/意见。非常感激。

  1. 该代码的CPU使用率是是通过屋顶。它使用了我的CPU的一半(两个内核中的每一个的50%)来完成这个任务,这比可比较的下载工具指数级地高出许多CPU。我对这个CPU使用来自哪里感到有些迷惑,因为我并不期待这一点。
  2. 通常情况下,显然落后于。其他2个线程将完成,之后它将占用第三个线程(这似乎主要是第一个线程的第一个线程)需要30秒或更长时间才能完成。我可以从任务管理器中看到,javaw进程仍在执行小IO写入,但我不知道为什么会发生这种情况(我猜测竞争条件?)。
  3. 尽管我选择了一个相当大的缓冲区(1MB),但我感觉InputStream几乎从未实际填充缓冲区,这会导致比我想要的更多的IO写入。我的印象是,在这种情况下,最好将IO访问保持在最低限度,但我不确定这是否是最好的方法。
  4. 我意识到Java可能不是完成这样的事情的理想语言,但我相信在我目前的实现中,要获得比我更高的性能。在这种情况下,NIO值得探索吗?

注:我使用Apache了HTTPClient做HTTP交互,这哪里是entity.getContent()来自(如果你想知道)。

+0

找到了很好的相关主题在这里:http://stackoverflow.com/questions/921262/how-to-download-and-save-a-file-from-internet-using-java 可能会给该当我回家时试试今晚:) – tmbrggmn 2010-08-05 07:31:04

+0

更新:CPU使用率高是由于ExecutorService isTerminated()方法调用的while()循环造成的。卫生署! – tmbrggmn 2010-08-09 07:42:32

+0

我认为很多还取决于网络配置以及网络接口卡(物理)。即使你有多个线程正在下载相同的文件,但是负责串行化字节的NIC也可能成为瓶颈! – TriCore 2016-05-11 04:02:25

回答

6

回答我的问题:

  1. 的CPU使用率增加是由于while() {}循环,在等待线程完成。事实证明,awaitTermination是一个更好的选择,等待Executor完成:)
  2. (和3和4)这似乎是野兽的本质;最后,我通过仔细同步每个下载一块数据的不同线程(特别是将这些块写入磁盘),实现了我想要做的事情。
2

我认为在Windows上的最佳性能是使用IO completions ports。我不知道的是(a)其他操作系统中是否有类似的概念,以及(b)是否有任何合适的Java包装?但是,如果可移植性对你来说不重要,那么可以用JNI推出自己的包装器。

3

推测Apache HTTP客户端会做一些缓冲,并有一个较小的缓冲区。它需要一个缓冲区来合理读取HTTP头,并且可能需要处理分块编码。

0

设置一个非常大的套接字接收缓冲区。但是真正的性能将受到网络带宽的限制,而不受CPU带宽的限制。你所做的只是为每个下载器分配1/3的网络带宽。如果你获得很多好处,我会感到惊讶。

+1

在传输开始时,三个连接可能比一个快。它需要TCP稍微寻找最佳的窗口大小,所以如果你使用并行连接,这个过程快3倍! – Karmastan 2010-08-05 05:23:03

+0

这就是我首先分块的原因,将文件分成3块可以使我快速下载相同的文件3次,假设单个块太慢而无法使连接饱和。尽管下载速度已经超过了,所以这不是问题。这是我担心的CPU使用率。它可能与代码在Eclipse中执行的事实有关吗? – tmbrggmn 2010-08-05 07:15:13

+3

好吧,你正在做N-1多余的寻求,最后一次我看着它,这是几十年前,寻求是一个惊人的昂贵的操作。每个作家只需要寻求一次;之后它只是顺序I/O。 – EJP 2010-08-05 13:15:27