Runtime.availableProcessors() 分析

最近看到一篇文章Docker面对Java将再也不尴尬:Java 10为Docker作了特殊优化,里面提到了java10对于docker作了一些特殊的优化。众所周知java的docker容器化支持一直以来都比较的尴尬,因为docker底层使用了cgroups来进行进程级别的隔离,虽然咱们经过docker设置了容器的资源限制,但jvm虚拟机其实感知不到这里些限制。好比咱们的宿主机多是8核16G,限定docker容器为2核4G,在容器中读出来的资源可能仍是8核16G,咱们平时可能会来读取机器资源来作性能优化,好比核心线程数、最大线程数的设定。这对于一些程序来说,在docker上跑可能会会带来性能损耗,所幸的是java10已经增长了这些支持,而且有jdk8兼容的计划。html

想起最近工做中,在优化程序过程当中发现availableProcessors彷佛有较大性能损耗,所以对它进行了详细的了解并作了一些测试。java

availableProcessors 提供了什么功能?

/**
     * Returns the number of processors available to the Java virtual machine.
     *
     * <p> This value may change during a particular invocation of the virtual
     * machine.  Applications that are sensitive to the number of available
     * processors should therefore occasionally poll this property and adjust
     * their resource usage appropriately. </p>
     *
     * @return  the maximum number of processors available to the virtual
     *          machine; never smaller than one
     * @since 1.4
     */
    public native int availableProcessors();
复制代码

jdk文档中这么写到,返回jvm虚拟机可用核心数。而且后面还有一段注释:这个值有可能在虚拟机的特定调用期间更改。咱们平时对于此函数的直观印象为:返回机器的CPU数,这个应该是一个常量值。由此看来,可能有很大的一些误解。由此我产生了两个疑问:linux

  • 一、何为JVM可用核心数?
  • 二、为什么返回值可变?它是如何工做的?

JVM可用核心数

这个比较好理解,顾名思义为JVM能够用来工做利用的CPU核心数。在一个多核CPU服务器上,可能安装了多个应用,JVM只是其中的一个部分,有些cpu被其余应用使用了。docker

为什么返回值可变?它是如何工做的?

返回值可变这个也比较好理解,既然多核CPU服务器上多个应用公用cpu,对于不一样时刻来说能够被JVM利用的数量固然是不一样的,既然如此,那java中是如何作的呢? 经过阅读jdk8的源码,linux系统与windows系统的实现差异还比较大。windows

linux 实现
int os::active_processor_count() {
  // Linux doesn't yet have a (official) notion of processor sets, // so just return the number of online processors. int online_cpus = ::sysconf(_SC_NPROCESSORS_ONLN); assert(online_cpus > 0 && online_cpus <= processor_count(), "sanity check"); return online_cpus; } 复制代码

linux 实现比较懒,直接经过sysconf读取系统参数,_SC_NPROCESSORS_ONLN。缓存

windows 实现
int os::active_processor_count() {
  DWORD_PTR lpProcessAffinityMask = 0;
  DWORD_PTR lpSystemAffinityMask = 0;
  int proc_count = processor_count();
  if (proc_count <= sizeof(UINT_PTR) * BitsPerByte &&
      GetProcessAffinityMask(GetCurrentProcess(), &lpProcessAffinityMask, &lpSystemAffinityMask)) {
    // Nof active processors is number of bits in process affinity mask
    int bitcount = 0;
    while (lpProcessAffinityMask != 0) {
      lpProcessAffinityMask = lpProcessAffinityMask & (lpProcessAffinityMask-1);
      bitcount++;
    }
    return bitcount;
  } else {
    return proc_count;
  }
}
复制代码

windows系统实现就比较复杂,能够看到不只须要判断CPU是否可用,还须要依据CPU亲和性去判断是否该线程可用该CPU。里面经过一个while循环去解析CPU亲和性掩码,所以这是一个CPU密集型的操做。性能优化

性能测试

经过如上分析,咱们基本能够知道这个操做是一个cpu敏感型操做,那么它的性能在各个操做系统下表现如何呢?以下我测试了该函数在正常工做何cpu满负荷工做状况下的一些表现。测试数据为执行100万次调用,统计10次执行状况,取平均值。相关代码以下:bash

public class RuntimeDemo {

    private static final int EXEC_TIMES = 100_0000;
    private static final int TEST_TIME = 10;

    public static void main(String[] args) throws Exception{
        int[] arr = new int[TEST_TIME];
        for(int i = 0; i < TEST_TIME; i++){
            long start = System.currentTimeMillis();
            for(int j = 0; j < EXEC_TIMES; j++){
                Runtime.getRuntime().availableProcessors();
            }
            long end = System.currentTimeMillis();
            arr[i] = (int)(end-start);
        }

        double avg = Arrays.stream(arr).average().orElse(0);
        System.out.println("avg spend time:" + avg + "ms");

    }
}
复制代码

CPU 满负荷代码以下:服务器

public class CpuIntesive {

    private static final int THREAD_COUNT = 16;

    public static void main(String[] args) {
        for(int i = 0; i < THREAD_COUNT; i++){
            new Thread(()->{
                long count = 1000_0000_0000L;
                long index=0;
                long sum = 0;
                while(index < count){
                    sum = sum + index;
                    index++;
                }
            }).start();
        }
    }
}
复制代码
系统 配置 测试方法 测试结果
Windows 2核8G 正常 1425.2ms
Windows 2核8G CPU 满负荷 6113.1ms
MacOS 4核8G 正常 69.4ms
MacOS 4核8G CPU满负荷 322.8ms

虽然两个机器的配置相差较大,测试数据比较意义不大,但从测试状况仍是能够得出以下结论:app

  • windows与类linux系统性能差别较大,与具体实现有关
  • CPU密集型计算对于该函数性能有较大的影响
  • 总体上讲,该函数性能仍是比较能够接受的,最长的那次为windows CPU满负荷下 也仅为6us。linux系统下能够降到ns级别。

总结

  • 平常工做中,并不太须要注意该函数的调用性能负荷
  • 如需使用通常定义成静态变量便可,对于cpu敏感性程序来说,能够经过相似缓存的策略来周期性获取该值
  • 工做中的性能问题可能并非该函数致使,多是其余问题致使

感谢

相关文章
相关标签/搜索