Linux環境下Java應用性能分析定位-CPU使用篇


1     CPU熱點分析定位背景

CPU資源還是很昂貴的,為了深刻感受到這種昂貴,間下圖當前CPU的資源售價:

 

所以對於程序猿們來說,需要讓程序合理高效的使用CPU資源。利用有限的CPU資源來解決完成我們面對的實際問題,這就是為什么我們要盡可能優化程序。

不篇從微觀層面展開介紹說明,不做宏觀層面的介紹(譬如數據中心級容量監控,管理調度(OpenStack,Kubernates等)以及遷移(手動,自動,冷遷,熱遷))。

本篇將以倒推式方法組織目錄結構。出現了問題怎么辦,以及為什么要這樣做,以及后續盡可能如何預防。

 

 

  • CPU熱點占用分析定位
  • 什么是CPU
  • Linux內核的進程調度
  • 反映CPU占用的相關性能指標
  • JVM的線程與Linux進程對應關系
  • Java並發編程

2     CPU熱點占用分析定位

 

當然也可以借助各種神器輔助定位,譬如JProfiler 等。

3     什么是CPU

CPU:解釋計算機指令以及處理計算機軟件中的數據。

計算機的性能在很大程度上由CPU的性能決定,而CPU的性能主要體現在其運行程序的速度上。影響運行速度的性能指標包括CPU的工作頻率、Cache容量、指令系統和邏輯結構等參數

主頻:主頻也叫時鍾頻率,單位是兆赫(MHz)或千兆赫(GHz),用來表示CPU的運算、處理數據的速度。通常,主頻越高,CPU處理數據的速度就越快。

CPU的主頻=外頻×倍頻系數。主頻和實際的運算速度存在一定的關系,但並不是一個簡單的線性關系。 所以,CPU的主頻與CPU實際的運算能力是沒有直接關系的,主頻表示在CPU內數字脈沖信號震盪的速度。CPU的運算速度還要看CPU的流水線、總線等各方面的性能指標。

外頻:外頻是CPU的基准頻率,單位是MHz。CPU的外頻決定着整塊主板的運行速度。通俗地說,在台式機中,所說的超頻,都是超CPU的外頻(當然一般情況下,CPU的倍頻都是被鎖住的)相信這點是很好理解的。但對於服務器CPU來講,超頻是絕對不允許的。前面說到CPU決定着主板的運行速度,兩者是同步運行的,如果把服務器CPU超頻了,改變了外頻,會產生異步運行,(台式機很多主板都支持異步運行)這樣會造成整個服務器系統的不穩定。

總線頻率:前端總線(FSB)是將CPU連接到北橋芯片的總線。前端總線(FSB)頻率(即總線頻率)是直接影響CPU與內存直接數據交換速度。有一條公式可以計算,即數據帶寬=(總線頻率×數據位寬)/8,數據傳輸最大帶寬取決於所有同時傳輸的數據的寬度和傳輸頻率

外頻與前端總線(FSB)頻率的區別:前端總線的速度指的是數據傳輸的速度,外頻是CPU與主板之間同步運行的速度。也就是說,100MHz外頻特指數字脈沖信號在每秒鍾震盪一億次;而100MHz前端總線指的是每秒鍾CPU可接受的數據傳輸量是100MHz×64bit÷8bit/Byte=800MB/s。

緩存大小也是CPU的重要指標之一,而且緩存的結構和大小對CPU速度的影響非常大,CPU內緩存的運行頻率極高,一般是和處理器同頻運作,工作效率遠遠大於系統內存和硬盤。實際工作時,CPU往往需要重復讀取同樣的數據塊,而緩存容量的增大,可以大幅度提升CPU內部讀取數據的命中率,而不用再到內存或者硬盤上尋找,以此提高系統性能。但是由於CPU芯片面積和成本的因素來考慮,緩存都很小。

L1 Cache(一級緩存)是CPU第一層高速緩存,分為數據緩存和指令緩存。內置的L1高速緩存的容量和結構對CPU的性能影響較大,不過高速緩沖存儲器均由靜態RAM組成,結構較復雜,在CPU管芯面積不能太大的情況下,L1級高速緩存的容量不可能做得太大。一般服務器CPU的L1緩存的容量通常在32-256KB。

L2 Cache(二級緩存)是CPU的第二層高速緩存,分內部和外部兩種芯片。內部的芯片二級緩存運行速度與主頻相同,而外部的二級緩存則只有主頻的一半。L2高速緩存容量也會影響CPU的性能,原則是越大越好,以前家庭用CPU容量最大的是512KB,筆記本電腦中也可以達到2M,而服務器和工作站上用CPU的

L2高速緩存更高,可以達到8M以上。

L3 Cache(三級緩存),分為兩種,早期的是外置,內存延遲,同時提升大數據量計算時處理器的性能。降低內存延遲和提升大數據量計算能力對游戲都很有幫助。而在服務器領域增加L3緩存在性能方面仍然有顯著的提升。比方具有較大L3緩存的配置利用物理內存會更有效,故它比較慢的磁盤I/O子系統可以處理更多的數據請求。具有較大L3緩存的處理器提供更有效的文件系統緩存行為及較短消息和處理器隊列長度

 

我們常說的核數,個數等之間什么關系呢?

術語

概述

查看指令

CPU個數

(socket )

就是實實在在插在主機上看得見摸得着那塊CPU硬件

cat /proc/cpuinfo| grep "physical id"| sort| uniq| wc -l

CPU核數

(core )

一塊物理CPU上能處理數據的芯片組數量。也就是說一個物理CPU上可能會有多個核心,日常中說的雙核,四核就是指的CPU核心

cat /proc/cpuinfo| grep "cpu cores"| uniq

超線程

(thread)

一個CPU核就是一個物理線程,由英特爾開發超線程技術可以把一個物理線程模擬出兩個線程來使用,使得單個核心用起來像兩個核一樣,以充分發揮CPU的性能

 

邏輯CPU

可簡單理解為一個處理單元,通常來說,總的邏輯CPU數對應總的CPU核數,但借助超線程技術,一個核用起來像兩個核,這時邏輯CPU數就是核心數的兩倍了

cat /proc/cpuinfo| grep "processor"| wc -l

vCPU

對於host來說,kvm虛擬機是一個進程,虛擬機的vcpu都是這個進程衍生出來的線程

不同的vcpu只是不同的線程,而不同的線程是跑在不同的cpu上的

 KVM 中,可以指定 socket,core 和 thread 的數目,比如 設置 “-smp 5,sockets=5,cores=1,threads=1”,則 vCPU 的數目為 5*1*1 = 5。客戶機看到的是基於 KVM vCPU 的 CPU 核,而 vCPU 作為 QEMU 線程被 Linux 作為普通的線程/輕量級進程調度到物理的 CPU 核上。

用戶希望把虛擬機的VCPU綁定在特定物理CPU上,VCPU只在綁定的物理CPU上調度,達到隔離VCPU並提升虛擬機性能的目的。如果沒有作VCPU綁定,則虛擬機的VCPU可以在所有物理CPU上調度(libvirt中cputune提供了精細的vcpu綁定設定,可以具體到每個vcpu設置。而且提供vcpu能力的標准化,如quota,period,shares,可以用於實現cpu的Qos)

宿主機上ps 指令可查看是否KVM虛擬機對應宿主的一個進程,而不同vCPU實際是此進程衍生的不同的線程

 

虛擬化場景下vCPU的親和性示意圖:

 

實踐:

1:Linux中CPU的主頻,總線頻率,外頻,Cache容量 如何查看

2:Linux中CPU個數,核數,是否開啟了超線程,超線程個數如何查看?

3:KVM虛擬化下如何設置vCPU親和性?

4:Docker容器化中如何設置CPU親和性?

4     反映CPU占用的相關性能指標

Linux CPU 占用率計算,都是根據/proc/stat 文件內容計算而來,在Linux/Unix 下,CPU 利用率分為用戶態、系統態和空閑態,  分別表示CPU 處於用戶態執行的時間,系統內核執行的時間,和空閑系統進程執行的時間。

 

原始指標

描述

備注

用戶時間(User time)

表示CPU 執行用戶進程的時間,包括nices時間。通常期望用戶空間CPU 越高越好。

 

系統時間(System time)

表示CPU 在內核運行時間,包括IRQ 和softirq 時間。系統CPU 占用率高,表明系統某部分存在瓶頸。通常值越低越好。

 

等待時間(Waiting time)

CPI 在等待I/O 操作完成所花費的時間。系統部應該花費大量時間來等待I/O 操作,否則就說明I/O 存在瓶頸。

干嘛的

空閑時間(Idle time)

系統處於空閑期,等待進程運行

干嘛的

Nice時間(Nice time)

系統調整進程優先級所花費的時間。

干嘛的

硬中斷處理時間(Hard Irq time)

系統處理硬中斷所花費的時間。

這個指標干嘛的

軟中斷處理時間(SoftIrq time)

系統處理軟中斷中斷所花費的時間

這個指標干嘛的

 

上述為Linux中CPU相關的原子指標,實際我們所說的占用率等都是統計指標,統計指標的解釋和計算如下:

統計指標

概述

計算公式

CPU時間

內核中的時間HZ是系統時鍾在一秒內固定發出時鍾中斷的次數。HZ在編譯內核前是可以進行配置的,因此通過下述命令就可以查看當前系統的時鍾中斷頻率:cat /boot/config-`uname -r` | grep CONFIG_HZ。tick為系統時鍾每“滴答“一次的時間,其值為(1/HZ)秒。也就是連續兩次時鍾中斷之間的時間間隔。jiffies用來計算自系統啟動以來tick的次數,也就是說系統時鍾每產生一次時鍾中斷,該變量的值就增加一次

user+system+nice+idle+iowait+irq+softirq+Stl

用戶態CPU占用率

用戶使用CPU的進程包括:cpu運行常規用戶進程,cpu運行niced process,cpu運行實時進程。一個Linux進程可以在用戶方式下執行,也可以在系統(內核)方式下執行,當一個進程在內核代碼中運行時,我們稱其處於內核態;當一個進程正在執行用戶自己的代碼時,我們稱其處於用戶態,在用戶方式下執行時,進程在它自己的應用應用代碼中執行,不需要內核資源來進行計算、管理內存或設置變量

(User time + Nice time)/CPU時間*100%

內核態CPU占用率

顯示了系統方式下所花費cpu時間的百分比,包括內核進程(kprocs)和其他需要訪問內核資源的進         程所消耗的cpu資源,系統使用cpu的進程包括:用於系統調用,用於I/O管理(中斷和驅動),用於內存管理(paging and swapping),用於進程管理(context switch and        process start),如果一個進程需要內核資源,它必須執行一個系統調用,並由此切換到系統方式從而使該資源可用

(System time + Hard Irq time +SoftIRQ time)/CPU時間*100%

空閑率

如果沒有線程可以執行(運行隊列為空),系統分派一個叫做wait的線程,可稱為idle kproc。如果ps報告顯示這個線程的總計時間較高,這表明存在時間段,其中沒有其它線程准備在cpu上運行或等待執行。系統因此大部分時間空閑或等待新任務

(Idle time)/CPU 時間*100%

 

上述涉及的幾個術語:用戶態,內核態,中斷。這就涉及了Linux內核的一些知識,主要是進程調度相關。

實踐:

  1. 1.      Linux中查看CPU使用的原始指標
  2. 2.      Linux中查看CPU使用的統計指標

5     Linux內核的進程調度

由前面介紹可知:CPU是完成具體計算的,但是CPU是硬件,對於上層應用程序來說不直接與CPU交互,而是通過操作系統來完成的,也就是應用程序實際通過與操作系統指令交互,有操作系統內部通過設備驅動與CPU交互完成相關計算任務的。

對於Linux,內部可分三層:硬件-內核-用戶空間

 

  1. 用戶模式+內核模式

一般說來,一個進程在CPU上運行可以有兩種運行模式,既可在用戶模式下運行,又可在內核模式下運行(即進程分別工作在用戶態和內核態,在內核態工作仍舊是這個進程,除非進行了進程的切換)。Linux整個內核就是由各種中斷和異常處理程序組成的。即,正常情況下處理器在用戶模式執行用戶程序,在中斷或異常情況下處理器切換到特權模式執行內核程序,處理完中斷或異常之后再返回用戶模式繼續執行用戶程序,例如,用戶進程A調用了內核系統調用來獲取當前的時鍾滴答數,在執行用戶進程A中的系統調用指令時會保存當前用戶進程的IP,CS等當前寄存器狀態,然后再跳轉到內核空間(即內核代碼區域)去執行像應的系統調用函數,獲取當前的時鍾滴答數。執行完后再通過IRET指令返回到進程A中(就是將進入時保存的信息再復位到相應的寄存器中),再接着從CS:EIP地址開始執行A進程的指令

  1. Linux調度方式:

Linux內核的調度方式基本上采用“搶占式優先級”方式,即當進程在用戶模式下運行時,不管是否自願,在一定條件下(如時間片用完或等待I/O),核心就可以暫時剝奪其運行而調度其它進程進入運行。但是,一旦進程切換到內核模式下運行,就不受以上限制而一直運行下去,直至又回到用戶模式之前才會發生進程調度。Linux系統中的調度策略基本上繼承了Unix的以優先級為基礎的調度。就是說,核心為系統中每個進程計算出一個優先權,該優先權反映了一個進程獲得CPU使用權的資格,即高優先權的進程優先得到運行。核心從進程就緒隊列中挑選一個優先權最高的進程,為其分配一個CPU時間片,令其投入運行。在運行過程中,當前進程的優先權隨時間遞減,這樣就實現了“負反饋”作用:經過一段時間之后,原來級別較低的進程就相對“提升”了級別,從而有機會得到運行。當所有進程的優先權都變為0時,就重新計算一次所有進程的優先權

  1. Linux調度算法:

Linux執行進程調度時,首先查找所有在就緒隊列中的進程,從中選出優先級最高且在內存的一個進程。如果隊列中有實時進程,那么實時進程將優先運行。如果最需要運行的進程不是當前進程,那么當前進程就被掛起,並且保存它的現場所涉及的一切機器狀態,包括程序計數器和CPU寄存器等,然后為選中的進程恢復運行現場

  1. CPU上下文切換

在切換時,一個進程存儲在處理器各寄存器中的中間數據叫做進程的上下文,所以進程的切換實質上就是被中止運行進程與待運行進程上下文的切換。在進程未占用處理器時,進程 的上下文是存儲在進程的私有堆棧中的。占用處理器時,是恢復到處理器的寄存器中去,並把待運行進程的斷點送入處理器的程序指針PC,於是待運行進程就開始被處理器運行了,也就是這個進程已經占有處理器的使用權了。進程的切換可以用中斷技術來實現

由上述可知:CPU的上下文切換時影響內核CPU占用率的關鍵因素。

6     JVM的線程與Linux進程對應關系

上面說的是Linux的進程調度,對於我們第一章節介紹的是分析Java應用的熱點線程,進程和Java線程,這到底怎么回事兒呢?

Java里的線程是由JVM來管理的,它如何對應到操作系統的線程是由JVM的實現來確定的。Linux 2.6上的HotSpot使用了NPTL機制,JVM線程跟內核輕量級進程有一一對應的關系線程的調度完全交給了操作系統內核,當然jvm還保留一些策略足以影響到其內部的線程調度,舉個例子,在linux下,只要一個Thread.run就會調用一個fork產生一個線程。

Java線程在Windows及Linux平台上的實現方式,是內核線程的實現方式。這種方式實現的線程,是直接由操作系統內核支持的——由內核完成線程切換,內核通過操縱調度器(Thread Scheduler)實現線程調度,並將線程任務反映到各個處理器上。內核線程是內核的一個分身。程序一般不直接使用該內核線程,而是使用其高級接口,即輕量級進程(LWP),也即線程

 

說明:KLT即內核線程Kernel Thread,是“內核分身”。每一個KLT對應到進程P中的某一個輕量級進程LWP(也即線程),期間要經過用戶態、內核態的切換,並在Thread Scheduler 下反應到處理器CPU上。)

在程序面上使用內核線程,必然在操作系統上多次來回切換用戶態及內核態;另外,因為是一對一的線程模型,LWP的支持數是有限的。

各個線程既可以共享進程資源(內存地址、文件I/O等),又可以獨立調度(線程是CPU調度的基本單位)。

從上述可知我們所說的Java的線程實際就是用戶態的輕量級進程,其映射到內核態的線程。(線程是CPU調度的基本單位)

7     Java並發

  • 線程數量

當Java線程數大於cpu線程數,操作系統使用時間片機制,采用線程調度算法,頻繁的進行線程切換。活躍線程數為 CPU(核)數時最佳。過少的活躍線程導致 CPU 無法被充分利用,過多的活躍線程導致過大的線程上下文切換開銷。線程應該是活躍的,處於 IO 的線程,休眠的線程等均不消耗 CPU。

I/O密集型 (IO-bound) 

I/O bound 指的是系統的CPU效能相對硬盤/內存的效能要好很多,此時,系統運作,大部分的狀況是 CPU 在等 I/O (硬盤/內存) 的讀/寫,此時 CPU Loading 不高。
CPU bound 指的是系統的 硬盤/內存 效能 相對 CPU 的效能 要好很多,此時,系統運作,大部分的狀況是 CPU Loading 100%,CPU 要讀/寫 I/O (硬盤/內存),I/O在很短的時間就可以完成,而 CPU 還有許多運算要處理,CPU Loading 很高。

我們現在做的開發大部分都是WEB應用,涉及到大量的網絡傳輸,不僅如此,與數據庫,與緩存間的交互也涉及到IO,一旦發生IO,線程就會處於等待狀態,當IO結束,數據准備好后,線程才會繼續執行。因此從這里可以發現,對於IO密集型的應用,我們可以多設置一些線程池中線程的數量,這樣就能讓在等待IO的這段時間內,線程可以去做其它事,提高並發處理效率。
    那么這個線程池的數據量是不是可以隨便設置呢?當然不是的,請一定要記得,線程上下文切換是有代價的。目前總結了一套公式,對於IO密集型應用:
    線程數 = CPU核心數/(1-阻塞系數)
    這個阻塞系數一般為0.8~0.9之間,也可以取0.8或者0.9。

計算密集型 (CPU-bound) 

在多重程序系統中,大部份時間用來做計算、邏輯判斷等CPU動作的程序稱之CPU bound。

在多核CPU時代,我們要讓每一個CPU核心都參與計算,將CPU的性能充分利用起來,這樣才算是沒有浪費服務器配置,如果在非常好的服務器配置上還運行着單線程程序那將是多么重大的浪費。對於計算密集型的應用,完全是靠CPU的核數來工作,所以為了讓它的優勢完全發揮出來,避免過多的線程上下文切換,比較理想方案是:
    線程數 = CPU核數+1
    也可以設置成CPU核數*2,這還是要看JDK的使用版本,以及CPU配置(服務器的CPU有超線程)。對於JDK1.8來說,里面增加了一個並行計算,計算密集型的較理想線程數 = CPU內核線程數*2

 

在Java中獲取CPU核數方法:Runtime.getRuntime().availableProcessors()

    • Wait,sleep,notify:阻塞,睡眠,喚醒。
    • Java多線程框架:ExecutorService等。
    • 線程間通信:信號量,內存隊列等
    • 線程安全:各種鎖,各種安全集合等


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM