【问题标题】:Why Is Java Not Utilising All My CPU Cores Effectively [duplicate]为什么 Java 没有有效地利用我所有的 CPU 内核 [重复]
【发布时间】:2014-01-15 12:46:47
【问题描述】:

我在一台四核 cpu 的机器上运行 Ubuntu。我编写了一些测试 Java 代码,它们生成给定数量的进程,这些进程在运行时为给定的迭代次数增加一个 volatile 变量。

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

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 使用所有内核,但我运行了top,它清楚地显示了 3 个线程,3 个内核以 ~100% 的速度运行,并且4个线程,4个核心被刷爆。

我的问题是:为什么在 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);
            }
        }
    }
}

【问题讨论】:

  • 这是一个有趣的问题,请发布您的Example 来源。
  • 我想其中一个线程也正在运行 JVM,然后生成了用于运行此代码的主线程。
  • 您的 cpu 是否有 4 个物理核心,或者它是否具有 2 个物理核心和 2 个逻辑核心的超线程?
  • @AndreyChaschev 编辑成问题。
  • 试着让volatile离开你的脸——删除它并使用其他机制来确保循环不会被优化——在最后打印temp或其他东西。 volatile 具有许多特定于 cpu 且跨线程有效的副作用。

标签: java multithreading concurrency cpu-usage multicore


【解决方案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 数量有限。

【讨论】:

  • 感谢您的回答。我已将示例代码编辑回问题中。对我来说,你提到的问题为什么会影响这一点并不明显。您能建议一种进一步调查的方法吗?
  • 您收到了很多赞。必须是 ` for (int i = 0; i stackoverflow.com/a/20809194/1083704
  • 这与复杂的 CPU 优化无关,他只是在双核上运行,由于超线程,报告为四核。
【解决方案2】:

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

【讨论】:

  • 我想就是这样。我只是尝试在我的带有 4 核 Intel Core i7-3610QM 的 Windows 机器上运行相同的程序,但时间并没有增加,因为我增加了在单独线程上完成的工作量。非常奇怪的是,时间并没有从 4 个线程增加到 5 个线程,然后从那里以线性速率增加。我想知道为什么 5 个线程以相同的速度运行,即使它们只在 4 个内核上运行?
  • 我刚刚在另一台装有 4 核 Intel Core i7-2600 的机器上再次运行它,这一次它的表现就好像它只有 3 个内核。即,当我使用 1、2 或 3 个线程时,它需要相同的时间,然后随着我运行它的更多线程而线性增加。我真的很难解释这种行为!
【解决方案3】:

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

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

  2. 锁定/争用问题(例如,如果线程必须不断地等待彼此完成)。

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

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

尝试将每个线程切换为处理自己的数据块,而不使用 volatile 变量。这应该会减少所有形式的争用。

【讨论】:

    【解决方案4】:

    如果您在 Core i5 上运行此程序(就像 Google 告诉我的联想 X1 Carbon 一样),那么您就有一台具有 2 个超核的 双核 机器。 i5 作为四核向操作系统(因此也向 Java)报告,因此超核就像真正的核一样使用,但所有这些都是为了加快线程上下文切换。

    这就是为什么您会在 2 个线程(每个真实内核 1 个)的执行时间上获得预期的最小差异,以及为什么时间不会随着额外线程线性增加,因为 2 个超内核会从真实内核中承受一些较小的负载核心。

    【讨论】:

      【解决方案5】:

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

      看你的处理器,英特尔的“四核”的大部分实际上是双核,模拟四核操作系统(是的,他们告诉你你有 4 核,但你只有 2事实...)。这是对您的问题的更好解释,因为时间会随着双核处理器的增加而增加。

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

      【讨论】:

        猜你喜欢
        • 2011-02-21
        • 1970-01-01
        • 2018-07-18
        • 1970-01-01
        • 2021-05-10
        • 2018-07-20
        • 1970-01-01
        • 2016-09-10
        • 1970-01-01
        相关资源
        最近更新 更多