4

我有一个应用程序接受队列上的工作,然后旋转该工作以在独立线程上完成。线程的数量并不多,比如最多 100 个,但这些都是密集型任务,可以快速将 CPU 提升到 100%。

为了以最快的速度完成最多的工作:当我需要做更多的工作并让 Java 线程调度程序处理分配工作时,我最好只启动更多线程,还是会变得更聪明并管理工作负载以保持 CPU 低于100% 让我更快?

这台机器专用于我的 java 应用程序。

编辑:

感谢您的精彩输入!

这些任务具有不同的复杂性并涉及 I/O,因此如果线程池较低(例如 4)很可能仅将 CPU 运行到 20%。我无法知道有多少任务实际上会将 CPU 占用到 100%。

我的想法是我应该通过 RMI 监视 CPU 并动态地上下拨动工作,还是我应该不在乎并让操作系统来处理它。

4

5 回答 5

15

如果您在并行线程中有太多同时计算密集型任务,您会很快达到收益递减点。事实上,如果有 N 个处理器(内核),那么您不想要超过 N 个这样的线程。现在,如果任务偶尔会因 I/O 或用户交互而暂停,那么正确的数字可能会更大一些。但是一般来说,如果在任何时候有更多的线程想要进行计算而不是可用的内核,那么你的程序就是在上下文切换上浪费时间——也就是说,调度会让你付出代价。

于 2012-04-12T02:11:10.663 回答
7

为了以最快的速度完成最多的工作:当我需要做更多的工作并让 Java 线程调度程序处理分配工作时,我最好只启动更多线程,还是会变得更聪明并管理工作负载以保持 CPU 低于100% 让我更快?

随着您添加越来越多的线程,上下文切换、内存缓存刷新、内存缓存溢出以及内核和 JVM 线程管理所产生的开销也会增加。当您的线程占用 CPU 时,它们的内核优先级下降到某个最小值,它们将达到时间片最小值。随着越来越多的线程挤占内存,它们会溢出各种内部 CPU 内存缓存。CPU 需要从较慢的内存中交换作业的可能性更高。在 JVM 内部,有更多的互斥锁本地争用,可能还有一些(可能很小)增量的每线程和对象带宽 GC 开销。根据用户任务的同步程度,更多线程会导致内存刷新和锁争用增加。

对于任何程序和任何架构,都有一个最佳点,线程可以优化利用可用的处理器和 IO 资源,同时限制内核和 JVM 开销。反复寻找最佳位置将需要多次迭代和一些猜测。

我建议使用Executors.newFixedThreadPool(SOME_NUMBER);并提交你的工作给它。然后,您可以进行多次运行,上下改变线程数,直到根据工作和盒子的架构找到同时运行的最佳池数。

但是请理解,最佳线程数将根据处理器数量和其他可能难以确定的因素而有所不同。如果它们在磁盘或网络 IO 资源上阻塞,则可能需要更多线程。如果他们正在做的工作主要是基于 CPU 的,那么线程会更少。

于 2012-04-12T02:11:08.380 回答
7

您的 CPU 以 100% 运行这一事实并不能说明他们在做有用的工作有多忙。在您的情况下,您使用的线程多于内核,因此 100% 包含一些上下文切换并不必要地使用内存(对 100 个线程的影响很小),这是次优的。

对于 CPU 密集型任务,我通常使用这个成语:

private final int NUM_THREADS = Runtime.getRuntime().availableProcessors() + 1;
private final ExecutorService executor = Executors.newFixedThreadPool(NUM_THREADS);

正如其他人所指出的,使用更多线程只会引入不必要的上下文切换。

显然,如果任务执行一些 I/O 和其他阻塞操作,这是不适用的,更大的池是有意义的。

编辑

为了回复@MartinJames 评论,我运行了一个(简单的)基准测试——结果表明,从池大小 = 处理器数量 + 1 到 100 只会稍微降低性能(我们称之为 5%)——更高的数字( 1000 和 10000)确实显着影响了性能。

结果是 10 次运行的平均值:
池大小:9:238 毫秒。//(NUM_CORES+1)
池大小:100:245 毫秒。
池大小:1000:319 毫秒。
池大小:10000:2482 毫秒。

代码:

public class Test {

    private final static int NUM_CORES = Runtime.getRuntime().availableProcessors();
    private static long count;
    private static Runnable r = new Runnable() {

        @Override
        public void run() {
            int count = 0;
            for (int i = 0; i < 100_000; i++) {
                count += i;
            }
            Test.count += count;
        }
    };

    public static void main(String[] args) throws Exception {
        //warmup
        runWith(10);

        //test
        runWith(NUM_CORES + 1);
        runWith(100);
        runWith(1000);
        runWith(10000);
    }

    private static void runWith(int poolSize) throws InterruptedException {
        long average = 0;
        for (int run = 0; run < 10; run++) { //run 10 times and take the average
            Test.count = 0;
            ExecutorService executor = Executors.newFixedThreadPool(poolSize);
            long start = System.nanoTime();
            for (int i = 0; i < 50000; i++) {
                executor.submit(r);
            }
            executor.shutdown();
            executor.awaitTermination(10, TimeUnit.SECONDS);
            long end = System.nanoTime();
            average += ((end - start) / 1000000);
            System.gc();
        }
        System.out.println("Pool size: " + poolSize + ": " + average / 10 + " ms.  ");
    }
}
于 2012-04-12T08:39:10.437 回答
2

“变得更聪明并管理工作负载以将 CPU 保持在 100% 以下会让我更快吗?”

可能不是。

正如其他人所发布的那样,如果大多数任务都是 CPU 密集型的,那么 100 个线程对于线程池来说太多了。它不会对典型系统的性能产生太大影响 - 如果过载太多,4个线程会很糟糕,400个线程会很糟糕。

你是如何决定 100 个线程的?为什么不是16,说?

'线程的数量并不多,比如说多达 100' - 它会有所不同吗?只需在启动时创建 16 个并停止管理它们 - 只需将队列传递给它们并忘记它们。

可怕的想法 - 你没有为每个任务创建一个新线程,是吗?

于 2012-04-12T09:25:55.243 回答
0

您应该保持 100% 的使用率,但使用最少的线程数。100 个线程看起来太多了。

于 2012-04-12T05:01:16.513 回答