在Java中是否可能检查CPU是否启用了超线程?

25
我想知道可以运行的最佳线程数。通常,这等于Runtime.getRuntime().availableProcessors()
然而,在支持超线程的CPU上,返回的数字会高出一倍。现在,对于某些任务,超线程是有好处的,但对于其他任务则无用。在我的情况下,我怀疑它没有用,所以我想知道是否需要将Runtime.getRuntime().availableProcessors()返回的数字除以二。
为此,我必须推断出CPU是否支持超线程。因此,我的问题是 - 如何在Java中实现?
谢谢。
编辑
好的,我已经对我的代码进行了基准测试。以下是我的环境:
  • 联想ThinkPad W510(即i7 CPU,带有4个内核和超线程),16G的RAM
  • Windows 7
  • 84个压缩的CSV文件,压缩大小从105M到16M不等
  • 所有文件都在主线程中一个接一个地读取 - 没有多线程访问硬盘。
  • 每个CSV文件行包含一些数据,解析数据并进行快速无上下文测试以确定该行是否相关。
  • 每个相关行包含两个双精度浮点数(表示经度和纬度),这些数被强制转换为单个Long,然后存储在共享哈希集合中。

因此,工作线程不会从硬盘读取任何内容,但它们会忙于解压缩和解析内容(使用opencsv库)。

以下是代码,没有无聊的细节:

public void work(File dir) throws IOException, InterruptedException {
  Set<Long> allCoordinates = Collections.newSetFromMap(new ConcurrentHashMap<Long, Boolean>());
  int n = 6;
  // NO WAITING QUEUE !
  ThreadPoolExecutor exec = new ThreadPoolExecutor(n, n, 0L, TimeUnit.MILLISECONDS, new SynchronousQueue<Runnable>());
  StopWatch sw1 = new StopWatch();
  StopWatch sw2 = new StopWatch();
  sw1.start();
  sw2.start();
  sw2.suspend();
  for (WorkItem wi : m_workItems) {
    for (File file : dir.listFiles(wi.fileNameFilter)) {
      MyTask task;
      try {
        sw2.resume();
        // The only reading from the HD occurs here:
        task = new MyTask(file, m_coordinateCollector, allCoordinates, wi.headerClass, wi.rowClass);
        sw2.suspend();
      } catch (IOException exc) {
        System.err.println(String.format("Failed to read %s - %s", file.getName(), exc.getMessage()));
        continue;
      }
      boolean retry = true;
      while (retry) {
        int count = exec.getActiveCount();
        try {
          // Fails if the maximum of the worker threads was created and all are busy.
          // This prevents us from loading all the files in memory and getting the OOM exception.
          exec.submit(task);
          retry = false;
        } catch (RejectedExecutionException exc) {
          // Wait for any worker thread to finish
          while (exec.getActiveCount() == count) {
            Thread.sleep(100);
          }
        }
      }
    }
  }
  exec.shutdown();
  exec.awaitTermination(1, TimeUnit.HOURS);
  sw1.stop();
  sw2.stop();
  System.out.println(String.format("Max concurrent threads = %d", n));
  System.out.println(String.format("Total file count = %d", m_stats.getFileCount()));
  System.out.println(String.format("Total lines = %d", m_stats.getTotalLineCount()));
  System.out.println(String.format("Total good lines = %d", m_stats.getGoodLineCount()));
  System.out.println(String.format("Total coordinates = %d", allCoordinates.size()));
  System.out.println(String.format("Overall elapsed time = %d sec, excluding I/O = %d sec", sw1.getTime() / 1000, (sw1.getTime() - sw2.getTime()) / 1000));
}

public class MyTask<H extends CsvFileHeader, R extends CsvFileRow<H>> implements Runnable {
  private final byte[] m_buffer;
  private final String m_name;
  private final CoordinateCollector m_coordinateCollector;
  private final Set<Long> m_allCoordinates;
  private final Class<H> m_headerClass;
  private final Class<R> m_rowClass;

  public MyTask(File file, CoordinateCollector coordinateCollector, Set<Long> allCoordinates,
                Class<H> headerClass, Class<R> rowClass) throws IOException {
    m_coordinateCollector = coordinateCollector;
    m_allCoordinates = allCoordinates;
    m_headerClass = headerClass;
    m_rowClass = rowClass;
    m_name = file.getName();
    m_buffer = Files.toByteArray(file);
  }

  @Override
  public void run() {
    try {
      m_coordinateCollector.collect(m_name, m_buffer, m_allCoordinates, m_headerClass, m_rowClass);
    } catch (IOException e) {
      e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
    }
  }
}

请查看以下结果(我稍微更改了输出以省略重复部分):
Max concurrent threads = 4
Total file count = 84
Total lines = 56395333
Total good lines = 35119231
Total coordinates = 987045
Overall elapsed time = 274 sec, excluding I/O = 266 sec

Max concurrent threads = 6
Overall elapsed time = 218 sec, excluding I/O = 209 sec

Max concurrent threads = 7
Overall elapsed time = 209 sec, excluding I/O = 199 sec

Max concurrent threads = 8
Overall elapsed time = 201 sec, excluding I/O = 192 sec

Max concurrent threads = 9
Overall elapsed time = 198 sec, excluding I/O = 186 sec

你可以得出自己的结论,但我的结论是,在我的具体情况下,超线程确实提高了性能。此外,对于这个任务和我的机器来说,拥有6个工作线程似乎是正确的选择。


1
有趣的问题。+1。我找到了一些可能会让你感兴趣的东西,尽管它可能不能回答你的问题。https://dev59.com/83VC5IYBdhLWcg3wrDNd - verisimilitude
2
如果在某些机器上,您的线程数是核心数的两倍,那么这是否会对您的应用程序性能产生明显或显著的影响? - Martin James
1
请注意,超线程并不会给你两个 CPU 核心 - 它只是更好地利用单个 CPU。 - Thorbjørn Ravn Andersen
1
对于开发人员来说,几乎不可能预见性能-例如,如果您的应用程序有很多缓存未命中(例如,如果它随机读取内存),它将受益于每个核心的多个硬件线程。您只能测量并检查资源是否被正确利用,没有任何魔法公式可以除以2或乘以Pi。 - Boris Treukhov
正如@ThorbjørnRavnAndersen所指出的,超线程技术主要是关于利用率的。如果你的CPU已经被充分利用,你将不会获得太多性能提升。不要被营销所迷惑。此外,对于某些代码,关闭超线程可能会获得更好的性能。 - codepk
显示剩余8条评论
7个回答

5

很遗憾,这在Java中是不可能实现的。如果您知道应用程序将在现代Linux变体上运行,可以读取文件/proc/cpuinfo并推断是否启用了HT。

使用以下命令读取输出:

grep -i "physical id" /proc/cpuinfo | sort -u | wc -l

1
+1 有用的信息,但遗憾的是代码是 Java 的,因此它可以在 Windows 和 Linux 上运行。 - mark
没有平台无关的方法来做到这一点。如果您拥有支持超线程的CPU,但已被禁用,则仍然可能看起来像您拥有超线程。 - Peter Lawrey
在某些虚拟配置下,所有的ID都为0,这意味着排序将会把计数减少到1。 - Axel Fontaine
如果您不进行排序,可能会再次计算HT核心,这将失去目的。 但是我仍然有同样的问题,即我的i7-3930K的物理ID始终返回0。 对我有效的方法是: grep -i“cpu cores”/proc/cpuinfo | sort -u - Kutzi
如果您有多个插座,每个插座都有多个核心,那么这种方法可能行不通。https://superuser.com/a/932418/294432 看起来是一个很好的解决方案,它基本上将插座数乘以每个插座的核心数。如果您有具有不同核心数量的插座,则可能无法正常工作,但我认为这是不太可能的情况。 - Rangi Keen

4

没有可靠的方法确定您的超线程是否打开、关闭或未开启。

相反,更好的方法是在第一次运行(或每次运行)时进行首次校准,运行第一个测试以确定使用哪种方法。

另一种方法是即使超线程没有帮助(只要不会使代码显著变慢),也可以使用所有处理器。


1
我不建议运行基准测试来进行校准,因为它们可能会受到GC或类编译的影响,在第一次运行时通常该进程没有其他任务,即在孤立环境中进行基准测试将是适得其反的。 - bestsss
如果您运行多个小的(约20毫秒至200毫秒)样本来执行CPU绑定任务,并选择最佳或中位数,您可以轻松消除GC或预热时间。 - Peter Lawrey
如果它确实是CPU绑定的,超线程很可能会失效。基准测试也需要某种形式的内存访问,这是嘈杂的部分。毫无疑问,可以通过基准测试进行估算,但除非预计该过程在其过程中具有非常相似的工作负载(包括内存带宽),否则即使我手动优化了基准测试,我也不会信任它。 - bestsss

4

以下是更多的思考:

  • 超线程可以使一个代码拥有多于两个线程(Sparc可以拥有8个)。
  • 垃圾收集器也需要CPU时间来工作。
  • 超线程可能有助于并发GC,也可能不会;或者JVM可能要求成为核心的独占者(而非超线程)。因此,在测试过程中阻碍GC以获得更好的结果可能会在长期内造成损失。
  • 如果存在缓存未命中,超线程通常是有用的,这样CPU就不会停顿而转向另一个任务。因此,“是否使用超线程”取决于负载和CPU L1/L2缓存大小/内存速度等因素。
  • 操作系统可能对某些线程有偏见或偏爱,而Thread.setPriority可能无法得到尊重(在Linux上通常无法得到尊重)。
  • 可以设置进程的亲和性,禁止某些核心的使用。因此,在这种情况下知道有超线程并没有任何显著的优势。

话虽如此:您应该有一个设置工人线程大小的设置,并根据架构的具体情况提供建议。


1

对于 Windows 系统,如果逻辑核心数高于物理核心数,则表示您已启用 超线程。了解更多信息请点击 这里

您可以使用 wmic 命令查找此信息:

C:\WINDOWS\system32>wmic CPU Get NumberOfCores,NumberOfLogicalProcessors /Format:List


NumberOfCores=4
NumberOfLogicalProcessors=8

因此,我的系统具有超线程技术。逻辑处理器的数量是核心数的两倍。
但您可能根本不需要知道这一点。Runtime.getRuntime().availableProcessors() 已经返回了逻辑处理器的数量。
获取物理核心数的完整示例(仅限 Windows):
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class PhysicalCores
{
    public static void main(String[] arguments) throws IOException, InterruptedException
    {
        int physicalNumberOfCores = getPhysicalNumberOfCores();
        System.out.println(physicalNumberOfCores);
    }

    private static int getPhysicalNumberOfCores() throws IOException, InterruptedException
    {
        ProcessBuilder processBuilder = new ProcessBuilder("wmic", "CPU", "Get", "NumberOfCores");
        processBuilder.redirectErrorStream(true);
        Process process = processBuilder.start();
        String processOutput = getProcessOutput(process);
        String[] lines = processOutput.split(System.lineSeparator());
        return Integer.parseInt(lines[2]);
    }

    private static String getProcessOutput(Process process) throws IOException, InterruptedException
    {
        StringBuilder processOutput = new StringBuilder();

        try (BufferedReader processOutputReader = new BufferedReader(
                new InputStreamReader(process.getInputStream())))
        {
            String readLine;

            while ((readLine = processOutputReader.readLine()) != null)
            {
                processOutput.append(readLine);
                processOutput.append(System.lineSeparator());
            }

            process.waitFor();
        }

        return processOutput.toString().trim();
    }
}

如果您展示如何在Java中获取物理核心数,那么您的回答就是我的问题的答案。 - mark

1

从纯Java中无法确定这一点(毕竟逻辑核心是核心,无论它是否使用HT实现)。请注意,迄今为止提出的解决方案可以解决您的要求(如您所请求的那样),但不仅英特尔CPU提供了一种形式的超线程(Sparc也是如此,我相信还有其他的)。

您还没有考虑到,即使您确定系统使用HT,您也无法使用Java控制线程与核心的亲和性。因此,您仍然受制于操作系统的线程调度程序。虽然有可能出现少量线程表现更好的情况(因为减少了缓存崩溃),但静态确定应使用多少线程是不可能的(毕竟,CPU具有非常不同的缓存大小(从低端256KB到服务器上的> 16MB的范围现在可以合理地预期。而且这将随着每一代的更新而改变)。

只需将其设置为可配置的设置,任何试图在不确切知道目标系统的情况下确定这一点的尝试都是徒劳的。


0

没有办法做到那样,你可以做的一件事是在应用程序中创建一个线程池,其中包含Runtime.getRuntime().availableProcessors()个线程,并在请求到来时使用。

这样,您就可以拥有0-Runtime.getRuntime().availableProcessors()个线程。


0

你可能无法可靠地查询操作系统或运行时,但可以运行快速基准测试。

逐步增加自旋锁线程,测试每个新线程是否与前一个一样迭代。一旦其中一个线程的性能低于前面测试的大约一半(至少对于英特尔而言,我不知道SPARC),则说明您已经开始与超线程共享核心。


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