1. 程式人生 > 程式設計 >Runtime.availableProcessors() 分析

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相容的計劃。

想起最近工作中,在優化程式過程中發現availableProcessors似乎有較大效能損耗,因此對它進行了詳細的瞭解並做了一些測試。

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數,這個應該是一個常量值。由此看來,可能有很大的一些誤解。由此我產生了兩個疑問:

  • 1、何為JVM可用核心數?
  • 2、為何返回值可變?它是如何工作的?

JVM可用核心數

這個比較好理解,顧名思義為JVM可以用來工作利用的CPU核心數。在一個多核CPU伺服器上,可能安裝了多個應用,JVM只是其中的一個部分,有些cpu被其他應用使用了。

為何返回值可變?它是如何工作的?

返回值可變這個也比較好理解,既然多核CPU伺服器上多個應用公用cpu,對於不同時刻來講可以被JVM利用的數量當然是不同的,既然如此,那java中是如何做的呢? 通過閱讀jdk8的原始碼,linux系統與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次執行情況,取平均值。相關程式碼如下:

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

雖然兩個機器的配置相差較大,測試資料比較意義不大,但從測試情況還是可以得出如下結論:

  • windows與類linux系統效能差異較大,與具體實現有關
  • CPU密集型計算對於該函式效能有較大的影響
  • 整體上講,該函式效能還是比較可以接受的,最長的那次為windows CPU滿負荷下 也僅為6us。linux系統下可以降到ns級別。

總結

  • 日常工作中,並不太需要注意該函式的呼叫效能負荷
  • 如需使用一般定義成靜態變數即可,對於cpu敏感性程式來講,可以通過類似快取的策略來週期性獲取該值
  • 工作中的效能問題可能並不是該函式導致,可能是其他問題導致

感謝