gpt4 book ai didi

java - 使用 ThreadPoolExecutor 时看不到 CPU Bound 任务的上下文切换开销

转载 作者:行者123 更新时间:2023-12-03 12:44:54 26 4
gpt4 key购买 nike

我正在尝试做一个简单的实验,当你有一堆 CPU 密集型任务时,我想找出线程池的正确大小。
我已经知道这个大小应该等于机器上的核心数,但我想通过经验证明这一点。这是代码:

public class Main {

public static void main(String[] args) throws ExecutionException {
List<Future> futures = new ArrayList<>();
ExecutorService threadPool = Executors.newFixedThreadPool(4);

long startTime = System.currentTimeMillis();

for (int i = 0; i < 100; i++) {
futures.add(threadPool.submit(new CpuBoundTask()));
}

for (int i = 0; i < futures.size(); i++) {
futures.get(i).get();
}

long endTime = System.currentTimeMillis();
System.out.println("Time = " + (endTime - startTime));
threadPool.shutdown();
}

static class CpuBoundTask implements Runnable {
@Override
public void run() {
int a = 0;
for (int i = 0; i < 90000000; i++) {
a = (int) (a + Math.tan(a));
}
}
}
}
每个任务在大约 700 毫秒内执行(我认为这足以被 ThreadScheduler 至少抢占一次)。
我在 MacbookPro 2017、3.1 GHz Intel Core i5、2 个激活超线程的物理内核上运行它,因此有 4 个逻辑 CPU。
我调整了线程池的大小,并多次运行该程序(平均时间)。结果如下:
1 thread = 57 seconds
2 threads = 29 seconds
4 threads = 18 seconds
8 threads = 18.1 seconds
16 threads = 18.2 seconds
32 threads = 17.8 seconds
64 threads = 18.2 seconds
由于上下文切换开销,我预计执行时间会显着增加,一旦我添加了这么多线程(超过 CPU 内核的数量),但似乎这并没有真正发生。
我使用 VisualVM 来监控程序,看起来所有线程都已创建并且它们处于运行状态,正如预期的那样。此外,CPU 似乎使用得当(接近 95%)。
有什么我想念的吗?

最佳答案

在这种情况下,您应该使用 System.nanoTime() instead of System.currentTimeMillis() .
您的算法在 4 处停止缩放线程,为简单起见,让我们假设所有线程执行相同数量的任务,因此每个线程 25 个。每个线程占用18或多或少的秒数来计算 25 次迭代。
以一种非常简单的方式,当您使用 64 运行时线程,每个内核将有 8 个线程,第一个 4迭代有4并行运行的线程(每个内核 1 个)和另一个 60线程处于空闲模式,等待 CPU 资源计算它们的迭代,所以你有类似的东西:

Iteration 0 : Thread 1 (running)
Iteration 1 : Thread 2 (running)
Iteration 2 : Thread 3 (running)
Iteration 3 : Thread 4 (running)
Iteration 4 : Thread 5 (waiting)
Iteration 5 : Thread 6 (waiting)
Iteration 6 : Thread 7 (waiting)
Iteration 7 : Thread 8 (waiting)
...
Iteration 63 : Thread 64 (waiting)
当那些 4线程完成它们的迭代,它们将分别获得另一个迭代。同时,让我们说,线程 58开始进行接下来的四次迭代( 再次是 4 个线程并行执行工作 ),而其他线程被阻塞等待 CPU,依此类推。所以你总是有 4线程并行运行,无论如何,这就是为什么:
8 threads = 18.1 seconds
16 threads = 18.2 seconds
32 threads = 17.8 seconds
64 threads = 18.2 seconds
您的执行时间大致相同,与 4 的执行时间大致相同线程完成 25并行迭代。
因为这是一个受 CPU 限制的算法,没有以下问题:
  • 同步;
  • 加载不平衡(即每次循环迭代花费大约相同的执行时间);
  • 内存带宽饱和;
  • 缓存失效;
  • 虚假分享。

  • 当您增加每个 core 的线程数时,它并没有反射(reflect)出太多的总体执行时间。 .

    关于java - 使用 ThreadPoolExecutor 时看不到 CPU Bound 任务的上下文切换开销,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/65370889/

    26 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com