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

我在一台具有四核CPU的机器上运行Ubuntu。我已经编写了一些测试Java代码,这些代码生成了给定数量的进程,这些进程只是在运行时为给定次数的迭代递增易失性变量。

我希望运行时间不会显着增加,而线程数小于或等于内核数,即4。实际上,这些是我从UNIX命令使用“实时”的时间:time

1 条线程:1.005 秒

2 条线程:1.018 秒

3 条线程:1.528 秒

4 条线程:1.982 秒

5 条线程:2.479 秒

6 条线程:2.934 秒

7 条针:3.356 秒

8 条主题:3.793 秒

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

起初,我认为这可能是因为操作系统阻止了JVM使用所有内核,但是我运行了,它清楚地表明,使用3个线程,3个内核以〜100%的速度运行,并且使用4个线程,4个内核被最大化。top

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

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

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++) {
            for (int j = 0; j < 1000000; j++) {
                this.temp += i + j;
            }
        }
    }

    int arg;
    int result;

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

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

    public static void main(String args[]) {

        // Get the number of threads (the command line arg)

        int numThreads = 1;
        if (args.length > 0) {
            try {
                numThreads = Integer.parseInt(args[0]);
            } catch (NumberFormatException nfe) {
                System.out.println("First arg must be the number of threads!");
            }
        }

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

答案 1

使用多个 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数量有限。


答案 2

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


推荐