java在CPU中的一些個破事

來源:互聯網
上載者:User

其實寫java的人貌似和CPU沒啥關係,最多最多和我們在前面提及到的如何將CPU跑滿、如何設定線程數有點關係,但是那個演算法只是一個參考,很多情境不同需要採取實際的手段來解決才可以;而且將CPU跑滿後我們還會考慮如何讓CPU不是那麼滿,呵呵,人類,就是這麼XX,呵呵,好了,本文要說的是其他的一些東西,也許你在java的寫代碼時幾乎不用關注CPU,因為滿足業務才是第一重要的事情,如果你要做到架構層級,為架構提供很多共用資料緩衝之類的東西,中間必然存在很多資料的徵用問題,當然java提供了很多concurrent包的類,你可以用它,但是它內部如何做的,你要明白細節才能用得比較好,否則還不如不用,本文可能不是闡述這些內容作為重點,因為如標題黨:我們要說CPU,呵呵。


還是那句話,貌似java和CPU沒有多少關係,我們現在來聊聊有啥關係;

1、當遇到共用元素,我們通常第一想法是通過volatile來保證一致性讀的操作,也就是絕對的可見度,所謂可見度,就是每次要使用該資料的時候,CPU不會使用任何cache的內容都會從記憶體中去抓取一次資料,並且這個過程對多CPU仍然有效,也就是相當CPU和記憶體之間此時是同步的,CPU會像匯流排發出一個Lock addl 0類似的的彙編指令,+0但相對於什麼都不會做;不過一旦該指令完成,後續操作將不再影響這個元素其他線程的訪問,也就是他能實現的絕對可見度,但是不能實現一致性操作,也就是說,volatile不能實現的是i++這類操作的一致性(在多線程下並發),因為i++操作是被分解為:

int tmp = i;tmp = tmp + 1;i = tmp;

這三個步驟來完成,從這點你也能看出i++為什麼能實現先做其他的事情再自我加1,因為它講值賦予給了另一個變數。


2、我們要用到多線程並發一致性,就需要用到鎖的機制,目前類似Atomic*的東西基本可以滿足這些要求,內部提供了很多unsafe類的方法,通過不斷對比絕對可見度的資料來保證擷取的資料是最新的;接下來我們繼續來說一些CPU其他的事情。


3、以前我們為了將CPU跑滿,但是無論如何跑不滿,因為我們開始說了忽略掉記憶體與CPU的延遲,今天既然提及到這裡,我們就簡單說下延遲,一般來講現在的CPU有三級cache,年代不同延遲不同,所以具體數字只能說個大概而已,現在的CPU一般一級cache的延遲在1-2ns,二級cache一般是幾個ns到十來ns左右,三級cache一般是30ns到50ns不等,記憶體訪問普遍會上到70ns甚至更多(電腦發展速度很快,這個值也僅僅在某些CPU上的資料,做一個範圍參考而已);別看這個延遲很小,都是納秒層級,你會發現你的程式被拆分為指令運算的時候,會有很多CPU互動,每次互動的延遲如果有這麼大的偏差,此時系統效能是會有變化的;


4、回到剛才說的volatile,它每次從記憶體中擷取資料,就是放棄cache,自然如果在某些單線程的操作中,會變得更加慢,有些時候我們也不得不這樣做,甚至於讀寫操作都要求一致性,甚至於整個資料區塊都被同步,我們只能在一定程度上降低鎖的粒度,但是不能完全沒有鎖,即使是CPU本身層級也會有指令層級的限制,如下:


5、在CPU本身層級的原子操作一般叫屏障,有讀屏障、寫屏障等,一般是基於一個點的觸發,當程式多條指令發送到CPU的時候,有些指令未必是按照程式的順序來執行,有些必須按照程式的順序來執行,只要能最終保證一致即可;在排序上,JIT在運行時會做改變,CPU指令層級也會做改變,原因主要是為了最佳化運行時指令讓程式跑得更快。


6、CPU層級會對記憶體做cache line的操作,所謂cache line會連續讀一塊記憶體,一般和CPU型號和架構有關係,現在很多CPU每次讀取連續記憶體一般是64byte,早期的有32byte的,所以在某些數組遍曆的時候會比較快(基於列遍曆很慢),但這個並不完全對,下面會對照一些相反的情況來說。


7、CPU對資料如果發生了修改,此時就不得不說CPU對資料修改的狀態,資料如果都被讀取,在多CPU下可以被多線程並行讀取並,當對資料區塊發生寫操作的時候,就不一樣了,資料區塊會有獨佔、修改、失效等狀態,資料修改後自然就會失效,當在多CPU下,多個線程都在對同一個資料區塊進行修改時,就會發生CPU之間的匯流排資料拷貝(QPI),當然如果修改到同一個資料上的時候我們是沒有辦法的,但是回到第6點的cache line裡面,問題就比較麻煩了,如果資料是在同一個數組上,而數組中的元素會被同時cache
line到一個CPU上的時候,多線程的QPI就會非常頻繁,有些時候即使是數組上組裝的是對象也會出現這個問題,如:

class InputInteger {   private int value;   public InputInteger(int i) {      this.value = i;   }}InputInteger[] integers = new InputInteger[SIZE];for(int i=0 ; i < SIZE ; i++) {   integers[i] = new InputInteger(i);}

此時你看出來integers裡面放的全部是對象,數組上只有對象的引用,但是對象的排布理論上說各自對象是獨立的,不會連續存放,不過java在指派至記憶體的時候,很多時候,在Eden地區是連續分配的,當在for迴圈的時候,如果沒有其他線程的接入,這些對象就會被存放在一起,即使被GC到OLD地區也很有可能會放在一起,所以靠簡單對象來解決cache line後還對整個數組修改的方式貌似不靠譜,因為int
是4位元組,如果在64模式下,這個大小是24位元組(有4byte補齊),指標壓縮開啟是16byte;也就是每次cpu可以看齊3-4個對象,如何讓CPUcache了,但是又不影響系統的QPI,別想通過分隔對象來完成,因為GC過程記憶體拷貝過程很可能會拷貝到一起,最好的辦法是補齊,雖然有點浪費記憶體,但是這是最靠譜的方法,就是將對象補齊到64位元組,上述若未開啟指標壓縮有24byte,此時還有40個位元組,只需要在對象內部增加5個long即可。

class InputInteger {   public int value;   private long a1,a2,a3,a4,a5;}

呵呵,這個辦法很土,不過很管用,有些時候,Jvm編譯的時候發現這幾個參數啥都沒做,就直接給你幹掉了,最佳化無效,土辦法加土辦法就是在一個方法體裡面簡單對這5個參數做一個操作(都用上),但是這個方法永遠不調用它即可。


8、在CPU這個層級有些時候就未必能先做盡量先做的道理為王者了,類似擷取鎖這種操作,在AtomicIntegerFieldUpdater的操作,如果調用getAndSet(true)在單線程下你會發現跑得還蠻快,在多核CPU下就開始變慢,為什麼上面說得很清楚了,因為getAndSet裡面是修改後對比,先改了再說,QPI會很高,所以這個時候,先做get操作,再修改才是比較好的做法;還有就是擷取一次,如果擷取不到,就讓步一下,讓其他的線程去做其他的事情;


9、CPU有些時候為瞭解決某些CPU忙和不繁忙的問題,會有很多演算法來解決,如NUMA是其中一種方案,不過不論哪種架構都在一定情境下比較有用,對有所有情境未必有效;有隊列鎖機制來完成對CPU狀態管理,不過這又存在了cache line的問題,因為狀態都是經常改變的,各類應用程式的核心為了配合CPU也會出一些演算法來做,使得CPU可以更加有效利用起來,如CLH隊列等。


有關這方面的細節會很多如用普通變數迴圈疊加和用volatile類型的做以及Atomic*系列的來做,完全是不一樣的;多維度數組迴圈,按照不同緯度向後次序來迴圈也是不一樣的,細節上點很多,明白為什麼就在實際最佳化過程中有靈感了;鎖的細節說太細很暈,在系統底層的層級,始終有一些輕量級的原子操作,不論誰說他的代碼是不需要加鎖的,最細的可以細到CPU在每個瞬間只能執行一條指令那麼簡單,多核心CPU在匯流排層級也會有共用區來控制一些內容,有讀層級、寫層級、記憶體層級等,在不同的情境下使得鎖的粒度盡量降低,那麼系統的效能不言而喻,很正常的結果。


本文就說到這裡,閑扯了下,僅供參考!


聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.