为什么Java没有有效地利用我所有的CPU内核

时间:2013-12-27 22:55:37

标签: java multithreading concurrency cpu-usage multicore

我在具有四核CPU的机器上运行Ubuntu。我编写了一些测试Java代码,它产生了一定数量的进程,这些进程只是在运行时为一定数量的迭代增加一个volatile变量。

我预计运行时间不会显着增加,而线程数小于或等于内核数量,即4.实际上,这些是我从UNIX {{实时'使用的时间{{ 1}}命令:

1个主题:1.005s

2个主题:1.018s

3个主题:1.528s

4个主题:1.982s

5个主题:2.479s

6个主题:2.934s

7个主题:3.356s

8个主题:3.793s

这表明添加一个额外的线程不会像预期的那样增加时间,但是的时间会增加3个和4个线程。

起初我认为这可能是因为操作系统阻止了JVM使用所有内核,但我运行了time,它清楚地显示有3个线程,3个内核运行在~100%,并且有4个线程,最多可以使用4个核心。

我的问题是:为什么在3/4 CPU上运行的代码与在1/2运行时的运行速度大致相同?因为 在所有核心上并行运行。

以下是我的主要参考方法:

top

5 个答案:

答案 0 :(得分:9)

使用多个CPU有助于达到使某些底层资源饱和的程度。

在您的情况下,底层资源不是CPU的数量,而是您拥有的L1缓存的数量。在你的情况下,你似乎有两个内核,每个内核都有一个L1数据缓存,因为你用易失性写入命中它,所以这是你的限制因素的L1缓存。

尝试使用

更少地访问L1缓存
public class Example implements Runnable {
    // using this so the compiler does not optimise the computation away
    volatile int temp;

    void delay(int arg) {
        for (int i = 0; i < arg; i++) {
            int temp = 0;
            for (int j = 0; j < 1000000; j++) {
                temp += i + j;
            }
            this.temp += temp;
        }
    }

    int arg;
    int result;

    Example(int arg) {
        this.arg = arg;
    }

    public void run() {
        delay(arg);
        result = 42;
    }

    public static void main(String... ignored) {

        int MAX_THREADS = Integer.getInteger("max.threads", 8);
        long[] times = new long[MAX_THREADS + 1];
        for (int numThreads = MAX_THREADS; numThreads >= 1; numThreads--) {
            long start = System.nanoTime();

            // Start up the threads

            Thread[] threadList = new Thread[numThreads];
            Example[] exampleList = new Example[numThreads];
            for (int i = 0; i < numThreads; i++) {
                exampleList[i] = new Example(1000);
                threadList[i] = new Thread(exampleList[i]);
                threadList[i].start();
            }

            // wait for the threads to finish

            for (int i = 0; i < numThreads; i++) {
                try {
                    threadList[i].join();
                    System.out.println("Joined with thread, ret=" + exampleList[i].result);
                } catch (InterruptedException ie) {
                    System.out.println("Caught " + ie);
                }
            }
            long time = System.nanoTime() - start;
            times[numThreads] = time;
            System.out.printf("%d: %.1f ms%n", numThreads, time / 1e6);
        }
        for (int i = 2; i <= MAX_THREADS; i++)
            System.out.printf("%d: %.3f time %n", i, (double) times[i] / times[1]);
    }
}

在我的双核,超线程笔记本电脑上,它以threads: factor

的形式生成
2: 1.093 time 
3: 1.180 time 
4: 1.244 time 
5: 1.759 time 
6: 1.915 time 
7: 2.154 time 
8: 2.412 time 

的原始测试相比较
2: 1.092 time 
3: 2.198 time 
4: 3.349 time 
5: 3.079 time 
6: 3.556 time 
7: 4.183 time 
8: 4.902 time 

过度使用的常见资源是L3缓存。这是在CPU之间共享的,虽然它允许一定程度的并发性,但它不能很好地扩展到CPU。我建议你查看你的示例代码正在做什么,并确保它们可以独立运行而不使用任何共享资源。例如大多数芯片的FPU数量有限。

答案 1 :(得分:8)

联想X1 Carbon中的Core i5不是四核处理器。它是具有超线程的双核处理器。当您只执行不会导致频繁,长管道停顿的简单操作时,超线程调度程序将没有太多机会将其他操作编织到停滞的管道中,您将看不到相当于四个实际内核的性能。

答案 2 :(得分:3)

有几件事可能会限制您多线程应用程序的效率。

  1. 资源的饱和度,例如内存/总线/等带宽。

  2. 锁定/争用问题(例如,如果线程经常不得不等待彼此完成)。

  3. 系统上运行的其他进程。

  4. 在您的情况下,您正在使用由所有线程访问的易失性整数,这意味着线程不断地必须在它们之间发送该整数的新值。这将导致某种程度的争用和内存/带宽使用。

    尝试将每个线程切换为在没有volatile变量的情况下处理自己的数据块。这应该减少所有形式的争用。

答案 3 :(得分:1)

如果您在Core i5上运行此功能(就像Google告诉我有关Lenovo X1 Carbon的那样),那么您就拥有了一台带有2个超级内核的双核机器。 i5向操作系统报告 - 因此也报告为Java - 作为四核,因此超级核心像真核一样使用,但所有这些都是为了加速线程上下文切换。

这就是为什么你得到2个线程(每个真实核心1个)的执行时间的预期最小差异,以及为什么时间不会随着其他线程线性增加,因为2个超级核心从实际中获得一些小负载芯

答案 4 :(得分:0)

你已经有两个很好的答案,两个都可以解释发生了什么。

看看你的处理器,来自intel的“四核”的大部分实际上都是双核,模拟四核做OS(是的,他们告诉你你有4核,但你只有2个)事实...)。这是对您的问题的更好解释,因为时间增加为双核处理器。

如果你有一个真正的4核心,另一个答案是你的代码有一些并发性。