Java - 多线程代码在更多内核上运行速度更快

时间:2011-06-30 21:06:32

标签: java multithreading

我只是在4核机器上运行一些多线程代码,希望它比单核机器更快。这是一个想法:我有一个固定数量的线程(在我的情况下,每个核心一个线程)。每个线程都执行以下形式的Runnable

private static int[] data; // data shared across all threads


public void run() {

    int i = 0;

    while (i++ < 5000) {

        // do some work
        for (int j = 0; j < 10000 / numberOfThreads) {
            // each thread performs calculations and reads from and
            // writes to a different part of the data array
        }

        // wait for the other threads
        barrier.await();
    }
}

在四核计算机上,此代码使用4个线程执行更糟比使用1个线程执行更糟。即使有CyclicBarrier的开销,我也会认为代码的执行速度至少要快2倍。为什么它

编辑:这是我尝试过的忙等待实现。不幸的是,它使程序在更多核心上运行得更慢(也在另一个问题here中讨论):

public void run() {

    // do work

    synchronized (this) {

        if (atomicInt.decrementAndGet() == 0) {

            atomicInt.set(numberOfOperations);

            for (int i = 0; i < threads.length; i++)
                threads[i].interrupt();
        }
    }

    while (!Thread.interrupted()) {}
}

5 个答案:

答案 0 :(得分:10)

添加更多线程并不一定能保证提高性能。使用其他线程可能会导致性能下降的原因有很多:

  • 粗粒度锁定可能过度序列化执行 - 也就是说,锁定可能导致一次只运行一个线程。您可以获得多个线程的所有开销,但没有任何好处。尽量减少锁定的持续时间。
  • 这同样适用于过于频繁的障碍和其他同步结构。如果内部j循环快速完成,您可能会将大部分时间花在屏障上。尝试在同步点之间做更多的工作。
  • 如果您的代码运行得太快,可能没有时间将线程迁移到其他CPU核心。除非你创建了许多非常短暂的线程,否则这通常不是问题。使用线程池,或者只是让每个线程更多工作可以提供帮助。如果您的线程每次运行超过一秒钟,这不太可能是一个问题。
  • 如果您的线程正在处理大量共享读/写数据,则缓存行反弹可能会降低性能。也就是说,虽然这通常会导致性能下降,但仅凭这一点不太可能导致性能比单线程情况更差。尝试确保每个线程写入的数据与其他线程的数据按缓存行的大小(通常约为64字节)分开。特别是,没有像[thread A, B, C, D, A, B, C, D ...]
  • 这样的输出数组

由于您尚未显示您的代码,因此我无法在此处详细说明。

答案 1 :(得分:4)

你正在睡觉纳秒而不是毫秒

我改变了

Thread.sleep(0, 100000 / numberOfThreads); // sleep 0.025 ms for 4 threads

Thread.sleep(100000 / numberOfThreads);

并且与正在开始的线程数成比例加速


我发明了一个CPU密集型的“countPrimes”。完整的测试代码here

我在四核机器上获得了以下加速:

4 threads: 1625
1 thread: 3747

(CPU负载监视器确实显示前一种情况下4个进程正忙,后一种情况下1个核心正忙。)

结论:您在同步之间的每个线程中执行相对较小的部分工作。同步比实际的CPU密集型计算工作花费的时间多得多。

(另外,如果您有内存密集型代码,例如线程中大量的数组访问,那么CPU无论如何都不会是瓶颈,你也看不到任何代码通过在多个CPU上拆分来加速。)

答案 2 :(得分:2)

runnable中的代码实际上没有做任何事情 在4个线程的具体示例中,每个线程将休眠2.5秒,并通过barier等待其他线程 所以所发生的一切都是每个线程都在处理器上增加i,然后阻塞睡眠,让处理器可用。
我不明白为什么调度程序会将每个线程分配到一个单独的核心,因为所有发生的事情都是线程大多等待。
期望仅使用相同的核心并在线程之间切换是公平合理的 的更新
刚刚看到你更新帖子说有些工作正在循环中发生。虽然你没有说,但是发生了什么。

答案 3 :(得分:2)

跨核心同步比在单核上同步

慢得多

因为在单个核心机器上,JVM不会在每次同步期间刷新缓存(非常慢速操作)

查看this blog帖子

答案 4 :(得分:1)

这是一个未经测试的SpinBarrier,但它应该可以工作。

检查案件是否有任何改善。由于您在循环中运行代码,因此如果您的内核处于空闲状态,则只会影响性能。 顺便说一句,我仍然相信你在计算,记忆密集的操作中有一个错误。你能告诉 你使用什么CPU + OS。

编辑,忘了版本。

import java.util.concurrent.atomic.AtomicInteger;

public class SpinBarrier {
    final int permits;
    final AtomicInteger count;
    final AtomicInteger version;
    public SpinBarrier(int count){ 
        this.count = new AtomicInteger(count);
        this.permits= count;
        this.version = new AtomicInteger();
    }

    public void await(){        
        for (int c = count.decrementAndGet(), v = this.version.get(); c!=0 && v==version.get(); c=count.get()){
            spinWait();
        }       
        if (count.compareAndSet(0, permits)){;//only one succeeds here, the rest will lose the CAS
            this.version.incrementAndGet();
        }
    }

    protected void spinWait() {
    }
}