与Java中的多处理相比,多线程性能较差

时间:2012-10-16 20:54:23

标签: java multithreading multiprocessing

假设我们有数百万条长文本必须要解析 在我的i7 2600 CPU上,每1000行解析大约需要13毫秒 因此,解析1,000,000行大约需要13秒 为了减少执行时间,我使用了多个线程进行管理 使用阻塞队列,我将1,000,000行作为一组1000块,每行包含1000行,并使用8个线程消耗块。代码很简单,但似乎工作正常,但性能并不令人鼓舞,大约需要11秒 以下是多线程代码的主要部分:

    for(int i=0;i<threadCount;i++)
    {
        Runnable r=new Runnable() {
            public void run() {
                try{
                    while (true){
                        InputType chunk=inputQ.poll(10, TimeUnit.MILLISECONDS);
                        if(chunk==null){
                            if(inputRemains.get())
                                continue;
                            else
                                return;
                        }
                        processItem(chunk);
                    }
                }catch (Exception e) {
                    e.printStackTrace();  
                }
            }
        };
        Thread t=new Thread(r);
        threadList.add(t);
        for(Thread t: threads)
            t.join();

我也使用过ExecutorService,但性能更差! 改变块大小也没有帮助,性能也没有改善 这意味着阻塞队列不是瓶颈 另一方面,当我同时运行4个串行程序实例时,所有4个实例完成只需15秒。这意味着我可以在15秒内使用4个过程处理4,000,0000行,因此,与1.2多线程加速相比,加速速度大约为3.4,这是非常有希望的。

我想知道有人对此有任何想法吗? 问题非常简单:阻塞队列中的一组行和几个线程,这些线程从队列中查询项并并行处理它们。队列最初被填充,因此线程完全忙 我之前也有类似的经历,但我无法弄清楚为什么多处理更好 我还要提一下,我在Windows 7上运行测试并使用1.7 JRE 任何想法都受到欢迎,并在此之前表示感谢。

4 个答案:

答案 0 :(得分:1)

修改

所以我最初认为你的时间安排在你的整个计划周围。如果只是计划之后行的处理时间已经被读入内存,那么可能是你的processItem(chunk);方法正在执行它的IO拥有或正在将信息写入synchronized对象或其他共享变量,这使得它无法同时运行。


  

我想知道有人对此有任何想法吗?

您的问题可能是IO bound不是 CPU板。通过添加更多线程来获得大幅提速的唯一方法是,如果您执行的CPU处理比执行磁盘读取(或写入)更多。一旦最大化了磁盘子系统的IO功能,就无法提高处理速度。如您所示,添加更多线程实际上可以减慢IO绑定程序。

我会添加一个额外的线程(即2个处理线程)来查看是否有帮助。如果您所获得的是2秒的速度改进,那么您将不得不将文件分成多个驱动器或将其移动到内存驱动器,如果这是一个重复的任务,以便能够更快地读取它。

  

我也使用过ExecutorService,但性能更差!

这可能是因为您使用了太多线程,或者每次迭代/块处理的行数太少。

  

另一方面,当我同时运行串行程序的4个实例时,只需15秒即可完成所有4个实例

我怀疑这是因为他们每个人都可以从操作系统中使用彼此的磁盘缓存。当第一个应用程序读取块#1时,其他3个应用程序不必。尝试复制文件4次,然后尝试在自己的文件中同时运行4个串行应用程序。你应该看到差异。

答案 1 :(得分:0)

我会责怪你的代码并行化。如果项目可用于处理,则多个线程将竞争相同的资源(队列)。争用同步锁是一种性能杀手。如果项目的处理速度快于它们被添加到队列中的速度,那么正在饥饿的线程几乎就是繁忙的循环,例如。 while (true) {}。这是因为您的轮询时间很短,当轮询失败时,您只需立即重试。

关于同步的一点注意事项。首先,JVM使用busy循环来等待资源变得可用,因为(通常)编写代码以尽快释放同步锁,并且替代(执行上下文切换)非常昂贵。最终,如果JVM发现它花费大部分时间等待同步锁定,那么如果它无法获取锁定,它将默认切换到不同的线程。

更好的解决方案是让一个线程读入数据并在每个线程都有可用插槽和新线程数据时调度新线程。这里Executor很有用,因为它可以跟踪哪些线程已经完成以及哪些仍然很忙。但伪代码看起来像是:

int charsRead;
char[] buffer = new char[BUF_SIZE];
int startIndex = 0;

while((charsRead = inputStreamReader.read(buffer, startIndex, buffer.length)
                != -1) {
    // find last new line so don't give a thread any partial lines
    int lastNewLine = findFirstNewLineBeforeIndex(buffer, charsRead);

    waitForAvailableThread(); // if not max threads running then should return 
    // immediately
    Thread t = new Thread(createRunnable(buffer, lastNewLine));
    t.start();
    addRunningThread(t);

    // copy any overshoot to the start of a new buffer
    // use a new buffer as the another thread is now reading from the previous 
    // buffer
    char[] newBuffer = new char[BUF_SIZE];
    System.arraycopy(buffer, lastNewLine+1, newBuffer, 0, 
        charsRead-lastNewLine-1);
    buffer = newBuffer;
}
waitForRemainingThreadsToTerminate();

答案 2 :(得分:0)

  

每1000行解析大约需要13毫秒。   因此,解析1,000,000行大约需要13秒。

jVM在完成10,000次之前不会预热,之后它可以快10到100倍,因此它可能是13秒或者可能是130毫秒或更短。

  

使用阻塞队列,我将1,000,000行作为一组1000个块,每个包含1000行,并使用8个线程消耗块。代码很简单,似乎工作正常,但性能并不令人鼓舞,大约需要11秒。

我建议你重新测试一个帖子,你可能会发现它只需不到11秒。

瓶颈是将String解析成一行并创建String对象所花费的时间,其余的只是在头顶上,而不是真正的瓶颈。


如果您阅读不同的文件,每个文件一个,您可以接近线性加速。读取行的问题是你必须一个接一个地阅读,并且你从并发中获得的好处很少。

答案 3 :(得分:0)

2600正在使用HT(超线程)进行8个线程..而且解析主要是内存工作,所以很少受益于HT ..