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敏感性程序來說,能夠經過相似緩存的策略來週期性獲取該值
  • 工做中的性能問題可能並非該函數致使,多是其餘問題致使

感謝

相關文章
相關標籤/搜索