天天看點

Java 7與僞共享的新仇舊恨

我提到了可以加入閑置的long字段來填充緩存行來避免僞共享。但是看起來java

7變得更加智慧了,它淘汰或者是重新排列了無用的字段,這樣我們之前的辦法在java

7下就不奏效了,但是僞共享依然會發生。我在不同的平台上實驗了一些列不同的方案,并且最終發現下面的代碼是最可靠的。(譯者注:下面的是最終版本,馬丁

在大家的幫助下修改了幾次代碼)

<code></code>

用以上這種辦法我獲得了和上一篇部落格裡提到的相近的性能,讀者可以把paddedatomiclong裡面那行填充物注釋掉再跑測試看看效果。

我想我們大家都有權去跟oracle投訴,讓他們在jdk裡預設加入緩存行對齊的函數或者是被填充好的原子類型,這和其他一些底層改變會讓java

成為一門真真正正的并發程式設計語言。我們一直以來不斷的在聽到他們講多核時代正在到來,但是我要說的是在這方面java需要快點趕上來。

———————————————–

(譯者注:博文後面的評論和交流也很精彩,也講述了這段示例代碼的進化過程,一起翻譯出來:)

1樓:ashwin jayaprakash

在前一篇博文中你建立了一個數組來放volatilelong,這次你又用一個數組放atomiclongarray(譯者注:此處我覺得他可能是寫錯了,應該是說atomiclong吧)。

但是如何能保證atomiclongarray或volatilelong會被緊挨着配置設定在記憶體中?

那麼,就算你在一個循環中建立他們,并且很幸運的,他們獲得了連續的記憶體空間,但是依然無法保證這四個執行個體會在堆空間裡緊挨着。如果他們被分布在jvm的舊生代堆裡并且沒有被壓實的話,直到一次主要gc壓實舊生代之前,重新配置設定填充是沒必要的,因為他們在堆中是分散的。

是以你最好對讀者說明,我們無法控制jvm如何在堆中對這些執行個體配置設定記憶體。(譯者注:沒辦法,要精确控制記憶體來保證性能的話就不要用java了,要不直接用c好了)

2樓:馬丁

你大體上說的是對的,ashwin,我們無法保證如何在堆空間中放置java對象,這是僞共享問題發生的根源。如果你有一些跨線程的指針或者計數

器,那麼確定他們在不同的緩存行中是非常重要的,否則的話程式就無法按cpu的核數擴充。填充的根本意義在于保護這些跨線程的指針和計數器,以確定他們在

不同的緩存行中。

這個是有意義的吧?

3樓:ashwin jayaprakash

嗯,有道理。那你可不可以建立一個大的atomiclongarray,然後讓不同的線程去更新第8,16,32個元素呢?(譯者注:也算是消除競

争的一個辦法,但是既然完全沒有競争還要多線程做什麼?)而不是搞四個atomiclongarray,而每個線程都去競争通路同一個數組元素。

謝謝馬丁花時間寫了這麼多。

4樓:馬丁

如果我可以提前知道更多的業務邏輯那麼你說的方式是可行的。但通常情況下在設計一個大型系統的時候,我們無法提前知道很多事情,或者我們要為其他的應用創造一個通用的類庫。

我很難為很多不同的上下文場景寫一個足夠小巧簡單的示例,而我上面寫的示例是為了說明當僞共享發生的時候有多糟糕。如果你在你的資料結構中做了填充,那麼

你就不必擔心他們在記憶體中如何配置設定。我們用一個更好的方案來替代atomiclong,并且你可以使用atomiclong的所有正常方法:

我是多希望java委員會可以認識到這個問題的嚴重性,并且在jdk裡加入對緩存行對齊和填充的基礎方法。這是在disruptor中有關性能bug的最大根源。

我也根據以上的回報更新了文章。

5樓:gil tene

馬丁,我很同意你的觀點,如果我們有一種辦法可以指定某個字段占有獨自的緩存行,并且讓jvm自動處理如何在對象布局上的正确填充,那這個世界會和諧的多。你搞的這個人造填充将會是很美好的一個事情,但是你也知道,實際上的對象布局情況要取決于jvm的特定實作。

我是一個偏執狂,我給你的填充方案裡加了一些東西,使那些個用于填充的字段很難被jvm優化掉。一個耍小聰明的jvm還是會把你用于填充的p1-p7的字

段優化掉,原理是這樣滴:paddedatomiclong類如果隻對final的falsesharing類可見(就是說

paddedatomiclong不能再被繼承了)。這樣一來編譯器就會“知道”它正在審視的是所有可以看到這個填充字段的代碼,這樣就可以證明沒有行為

依賴于p1到p7這些字段。那麼“聰明”的jvm會把上面這些絲毫不占地方的字段統統優化掉。

那麼針對這樣的情況,你可以巧妙的讓paddedatomiclong類在falsesharing類之外可見,比如直接加一個依賴于p1到p7的公開的通路函數,并且這個函數在理論上可以被外界通路到。

6樓:馬丁

我根據gil的回報做了修改。

7樓:stanimir simeonoff

直接用一個數組并且把元素放在中間的位置上(或者直接用bytebuffer,而你卻為了這個寫了這麼一大篇),java是不會重排他們的,我就是這樣來消除僞共享的。

8樓:馬丁

我以前經常像你這麼幹,比如搞一個長度是15的數組,把元素放在正中間,但是,如果我們需要volatile這個語意就行不通了。對于你的情況來說,你隻

需要用atomiclongarray或者類似的。根據我的測量,在一個算法中,這種間接引用(譯者注:原詞是indirection,我了解也許是指間

接引用,即不是直接使用數組,而是使用atomiclongarray這種包裝過的數組)和邊界檢查的消耗是顯著的。

據我所知,一些人建議加入@contened注解來标記一個字段,讓這個被标記的字段擁有獨立的緩存行,我希望這個快點到來。

8.1樓:john

你好,馬丁,我看到在disruptor目前的版本中sequence類用的是unsafe.compareandswaplong(..)來更新第七個下标的long。

為什麼不數組的長度不是15或者是其他的數值?如果長度是15的話會把2級緩存的緩存行也填充掉麼?

謝謝。

8.2樓:馬丁

因為用7個下标保證了會有56個位元組填充在數值的任何一邊,56位元組的填充+8位元組的long數值正好裝進一行64位元組的緩存行。

9樓:stanimir simeonoff

是的,馬丁,我指的就是atomiclongarray,如果你不想為間接引用和邊界檢查買單,unsafe 是一個選項(甚至總是這樣)。

10樓:mohan radhakrishnan

哪裡有一些簡單硬體說明書是講述緩存行的關鍵概念麼?我想找一些插圖什麼的來了解核心和緩存直接如何互動造成了僞共享。

11樓:馬丁

你可以參照下面這個pdf的第3和第4章:

<a href="http://img.delivery.net/cm50content/intel/productlibrary/100412_parallel_programming_02.pdf">http://img.delivery.net/cm50content/intel/productlibrary/100412_parallel_programming_02.pdf</a>

12樓:ying

你的部落格太nb了,多謝馬丁,我關于填充有兩個疑問:

1.long占8位元組,對象引用占16位元組,但是這個實作是

<code>1</code>

<code>public</code> <code>final</code> <code>static</code> <code>class</code> <code>volatilelong</code><code>// 16byte&lt;/pre&gt;</code>

<code>2</code>

<code>{</code>

<code>3</code>

<code>public</code> <code>volatile</code> <code>long</code> <code>value = 0l;</code><code>// 8 byte</code>

<code>4</code>

<code>public</code> <code>long</code> <code>p1, p2, p3, p4, p5, p6;</code><code>// 6*8 = 48byte</code>

<code>5</code>

<code>}</code>

看起來好像是72個位元組啊。

2.你是發現這個問題的?是去查彙編代碼嗎?

12.1樓:馬丁

我不希望在緩存行中的标記字在取出鎖或者垃圾回收器在老化對象的時候被修改。

就算預設啟用64位模式的壓縮指針,它還是會包含類指針在對象的頭部。

<a href="https://wikis.oracle.com/display/hotspotinternals/compressedoops">https://wikis.oracle.com/display/hotspotinternals/compressedoops</a>

這個僞共享的問題我是在多年前發現的,但是我為一個應用做性能測試,發現性能時高時低,追查原因下去發現是僞共享問題。

12.2樓:ying

那你是如何縮小問題的範圍最後發現問題的呢?需要深入分析彙編代碼麼?

11.3樓:馬丁

彙編代碼是不會顯示出問題的,你需要去追查為什麼cpu的2級緩存總是不命中,追查下去就知道了。

13樓:joachim

關于@contended注解的提案在這裡:

<a href="http://mail.openjdk.java.net/pipermail/hotspot-dev/2012-november/007309.html">http://mail.openjdk.java.net/pipermail/hotspot-dev/2012-november/007309.html</a>

牛文啊,贊!