为什么这段Java代码不能利用所有CPU核心?

15

这段简单的Java代码应该可以在正确的参数下加载所有可用的CPU核心。例如,您可以使用以下命令启动它:

java VMTest 8 int 0

它将启动8个线程,除了循环并将2添加到整数之外,不会做任何其他事情。这是在寄存器中运行甚至不分配新内存的操作。

我们现在面临的问题是:即使运行24个线程(当然也是类似2个12个线程或小型机器),我们也无法使一个24核心的机器负载满(AMD带有12个核心的2个插槽)。因此我们怀疑JVM(Sun JDK 6u20 on Linux x64)不太具备可扩展性。

是否有人看到过类似的情况或有能力运行它并报告它是否在他/她的机器上良好运行(仅限>= 8个核心)?有什么想法?

我也尝试了在拥有8个核心的Amazon EC2上运行它,但虚拟机似乎与真实的计算机不同,因此负载的表现完全奇怪。

package com.test;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

public class VMTest
{
    public class IntTask implements Runnable 
    {
        @Override
        public void run()
        {
            int i = 0;

            while (true)
            {
                i = i + 2;
            }
        }
    }
    public class StringTask implements Runnable 
    {
        @Override
        public void run()
        {
            int i = 0;

            String s;
            while (true)
            {
                i++;
                s = "s" + Integer.valueOf(i);
            }
        }
    }
    public class ArrayTask implements Runnable 
    {
        private final int size; 
        public ArrayTask(int size)
        {
            this.size = size;
        }
        @Override
        public void run()
        {
            int i = 0;

            String[] s;
            while (true)
            {
                i++;
                s = new String[size];
            }
        }
    }

    public void doIt(String[] args) throws InterruptedException
    {
        final String command = args[1].trim();

        ExecutorService executor = Executors.newFixedThreadPool(Integer.valueOf(args[0]));
        for (int i = 0; i < Integer.valueOf(args[0]); i++)
        {
            Runnable runnable = null;
            if (command.equalsIgnoreCase("int"))
            {
                runnable = new IntTask();
            }
            else if (command.equalsIgnoreCase("string"))
            {
                runnable = new StringTask();
            }
            Future<?> submit = executor.submit(runnable);
        }
        executor.awaitTermination(1, TimeUnit.HOURS);
    }

    public static void main(String[] args) throws InterruptedException
    {
        if (args.length < 3)
        {
            System.err.println("Usage: VMTest threadCount taskDef size");
            System.err.println("threadCount: Number 1..n");
            System.err.println("taskDef: int string array");
            System.err.println("size: size of memory allocation for array, ");
            System.exit(-1);
        }

        new VMTest().doIt(args);
    }
}

额外信息。刚刚发现JDK的64位版本比32位版本更好地加载核心(约90%与约45%)。这很奇怪,因为操作系统和AMD CPU支持32位,并且在该测试期间我没有运行任何内存操作。 - ReneS
1
仅供理解 - 为什么您不使用invokeAll(..)方法?另外,为什么不使用可调用对象?据我所知,Runnable不是java.concurrent的一部分。 - InsertNickHere
你还应该注意其他正在运行的进程。你是否进行了“干净”的运行,没有其他程序/进程占用CPU时间? - InsertNickHere
你的本地版本的“int”任务在加载核心方面表现如何?“string” / “int”的区别是什么?发布一些摘要数字? - Justin
@InsertNickHere:只是想快速编写Java代码来加载CPU以证明一个理论。是一次顺利的运行。 @Justin:我没有本地版本。这是为了检查Java是否可以在理论上处理核心以及使用最简单的代码。 - ReneS
你需要创建多少线程才能充分利用所有的核心? - Thorbjørn Ravn Andersen
5个回答

10

我认为你的代码没有任何问题。

然而,遗憾的是,你无法在Java中指定处理器亲和性。因此,这实际上是由操作系统而不是JVM来处理的。它完全取决于你的操作系统如何处理线程。

你可以将Java线程分成单独的进程,并将它们封装在本地代码中,以每个核心一个进程的方式运行。当然,这会使通信变得复杂,因为它将是进程间而不是线程间的通信。这就是像boink这样的流行格网计算应用程序的工作原理。

否则,你只能受制于操作系统对线程的调度。


这不是问题的关键,使用较少线程运行两个虚拟机也不会对游戏产生太大影响。 - ReneS
每个核心运行一个JVM,每个JVM有两个线程怎么样?你可能想改变你的问题,因为我已经回答了“为什么这个Java代码没有利用所有的CPU核心?” - Marcus Adams
很抱歉有所误解,但问题是:“...这个Java代码...”。它很简单,没有锁,没有同步,没有内存分配,仍然不会占用100%的CPU。这应该创建X个并发且独立的线程。 - ReneS

4

如果实现了这个选项,它应该解决亲和性问题。 - Justin
谢谢提供幻灯片和程序的提示。我会尝试一下。 - ReneS
似乎做了一些事情,但很棘手。还有其他的标志,比如-XX:+UseTLAB和-XX:+UseNUMA,可以应用于虚拟机,以更好地与底层架构配合工作。 - ReneS

2

看起来你的虚拟机正在运行所谓的“客户端”模式,在该模式下,所有Java线程都映射到一个本地操作系统线程上,因此由一个单独的CPU核心运行。尝试使用-server开关调用JVM,这将解决问题。

如果你遇到以下错误:Error: no 'server' JVM found,那么你需要从JDK的jre\bin目录中复制server目录到 JRE的bin目录。


1

uname -a 2.6.18-194.11.4.el5 #1 SMP Tue Sep 21 05:04:09 EDT 2010 x86_64 x86_64 x86_64 GNU/Linux

Intel(R) Xeon(R) CPU E5530 @ 2.40GHz http://browse.geekbench.ca/geekbench2/view/182101

Java 1.6.0_20-b02

16个核心,程序通过vmstat显示消耗了100%的CPU。

有趣的是,我来到这篇文章是因为我怀疑我的应用程序没有利用所有的核心,因为CPU利用率从未增加,但响应时间开始恶化。


0

我注意到即使在C语言中,紧密的循环经常会出现这样的问题。您还会看到不同操作系统之间存在相当大的差异。

根据您使用的报告工具,它可能不会报告某些核心服务使用的CPU。

Java倾向于非常友好。您可以尝试在Linux中进行相同的操作,但将进程优先级设置为负数,然后观察其行为。

如果您的JVM没有使用绿色线程,则在应用程序内设置线程优先级也可能有所帮助。

有很多变量。


1
谢谢,但是没有任何虚拟机在典型平台上使用绿色线程,比如Windows,Linux,Solaris和MacOS,至少据我所知。 - ReneS
@ReneS 在过去的几年里,我一直在有线电视盒和频谱分析仪中使用Java嵌入式技术--我不会轻易做出假设--我只是想说需要考虑很多因素。 - Bill K
没问题,感谢您的评论。我已经很长时间没有在我的Java世界里看到绿色线程了。 - ReneS

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接