java - 網頁標題 - 是不穩定的昂貴?




title tag中文 (3)

閱讀JSR-133編譯器編寫者手冊,了解易失性的實現,尤其是“與原子指令的交互”部分我假設讀取一個易失性變量而不更新它需要一個LoadLoad或一個LoadStore屏障。 在頁面的下方,我看到LoadLoad和LoadStore在X86 CPU上是無效的。 這是否意味著可以在x86上執行易失性讀取操作而無需顯式高速緩存失效,並且與正常變量讀取一樣快(忽略易失性的重新排序約束)?

我相信我沒有正確理解這一點。 有人可以照顧開導我嗎?

編輯:我不知道在多處理器環境中是否有差異。 正如John V.指出的那樣,在單CPU系統上,CPU可能會看它自己的線程緩存,但在多CPU系統中,CPU必須有一些配置選項,這是不夠的,主內存必須被擊中,使得volatile變慢在多CP​​U系統上,對嗎?

PS:在我學習更多關於這方面的方法中,我偶然發現了以下很棒的文章,因為這個問題可能對其他人很有趣,所以我會在這里分享我的鏈接:


一般來說,在大多數現代處理器上,易失性負載與正常負載相當。 一個不穩定的商店大約是一個單一進入/監控退出時間的三分之一。 這是在緩存一致的系統上看到的。

為了回答OP的問題,易失性寫入是昂貴的,而讀取通常不是。

這是否意味著在x86上沒有顯式高速緩存失效可以完成易失性讀操作,並且是一個快速的正常變量讀取(忽略volatile的重新排序約束)?

是的,有時在驗證某個字段時,CPU甚至可能不會訪問主內存,而是監視其他線程緩存並從中獲取值(非常一般的解釋)。

然而,我第二次尼爾的建議,如果你有一個字段訪問多個線程你shold包裝它作為一個AtomicReference。 作為一個AtomicReference,它執行的讀/寫吞吐量大致相同,但更明顯的是該字段將被多個線程訪問和修改。

編輯回答OP的編輯:

高速緩存一致性是一個複雜的協議,但總之:CPU將共享連接到主內存的公共高速緩存行。 如果一個CPU加載內存,並且沒有其他CPU擁有該CPU,它將認為它是最新的值。 如果另一個CPU試圖加載相同的內存位置,則已加載的CPU將會知道這一點,並將緩存的引用實際共享給請求的CPU - 現在請求CPU在其CPU緩存中擁有該內存的副本。 (它從來不需要在主內存中查找參考)

涉及的協議有很多,但是這給出了正在發生的事情的概念。 同樣為了回答您的其他問題,在沒有多個處理器的情況下,使用多個處理器時,實際上易失性讀取/寫入可能會更快。 有一些應用程序實際上可以同時運行更快的單個CPU和多個應用程序。


在英特爾,一個非競爭性的易失性讀取相當便宜。 如果我們考慮以下簡單情況:

public static long l;

public static void run() {        
    if (l == -1)
        System.exit(-1);

    if (l == -2)
        System.exit(-1);
}

使用Java 7打印彙編代碼的能力,run方法看起來像這樣:

# {method} 'run2' '()V' in 'Test2'
#           [sp+0x10]  (sp of caller)
0xb396ce80: mov    %eax,-0x3000(%esp)
0xb396ce87: push   %ebp
0xb396ce88: sub    $0x8,%esp          ;*synchronization entry
                                    ; - Test2::[email protected] (line 33)
0xb396ce8e: mov    $0xffffffff,%ecx
0xb396ce93: mov    $0xffffffff,%ebx
0xb396ce98: mov    $0x6fa2b2f0,%esi   ;   {oop('Test2')}
0xb396ce9d: mov    0x150(%esi),%ebp
0xb396cea3: mov    0x154(%esi),%edi   ;*getstatic l
                                    ; - Test2::[email protected] (line 33)
0xb396cea9: cmp    %ecx,%ebp
0xb396ceab: jne    0xb396ceaf
0xb396cead: cmp    %ebx,%edi
0xb396ceaf: je     0xb396cece         ;*getstatic l
                                    ; - Test2::[email protected] (line 37)
0xb396ceb1: mov    $0xfffffffe,%ecx
0xb396ceb6: mov    $0xffffffff,%ebx
0xb396cebb: cmp    %ecx,%ebp
0xb396cebd: jne    0xb396cec1
0xb396cebf: cmp    %ebx,%edi
0xb396cec1: je     0xb396ceeb         ;*return
                                    ; - Test2::[email protected] (line 40)
0xb396cec3: add    $0x8,%esp
0xb396cec6: pop    %ebp
0xb396cec7: test   %eax,0xb7732000    ;   {poll_return}
;... lines removed

如果你看到2個引用getstatic,第一個涉及從內存加載,第二個跳過負載,因為值已從它已經加載到的寄存器中重用(長是64位,在我的32位筆記本電腦上它使用2個寄存器)。

如果我們使得變量易變,那么生成的程序集是不同的。

# {method} 'run2' '()V' in 'Test2'
#           [sp+0x10]  (sp of caller)
0xb3ab9340: mov    %eax,-0x3000(%esp)
0xb3ab9347: push   %ebp
0xb3ab9348: sub    $0x8,%esp          ;*synchronization entry
                                    ; - Test2::[email protected] (line 32)
0xb3ab934e: mov    $0xffffffff,%ecx
0xb3ab9353: mov    $0xffffffff,%ebx
0xb3ab9358: mov    $0x150,%ebp
0xb3ab935d: movsd  0x6fb7b2f0(%ebp),%xmm0  ;   {oop('Test2')}
0xb3ab9365: movd   %xmm0,%eax
0xb3ab9369: psrlq  $0x20,%xmm0
0xb3ab936e: movd   %xmm0,%edx         ;*getstatic l
                                    ; - Test2::[email protected] (line 32)
0xb3ab9372: cmp    %ecx,%eax
0xb3ab9374: jne    0xb3ab9378
0xb3ab9376: cmp    %ebx,%edx
0xb3ab9378: je     0xb3ab93ac
0xb3ab937a: mov    $0xfffffffe,%ecx
0xb3ab937f: mov    $0xffffffff,%ebx
0xb3ab9384: movsd  0x6fb7b2f0(%ebp),%xmm0  ;   {oop('Test2')}
0xb3ab938c: movd   %xmm0,%ebp
0xb3ab9390: psrlq  $0x20,%xmm0
0xb3ab9395: movd   %xmm0,%edi         ;*getstatic l
                                    ; - Test2::[email protected] (line 36)
0xb3ab9399: cmp    %ecx,%ebp
0xb3ab939b: jne    0xb3ab939f
0xb3ab939d: cmp    %ebx,%edi
0xb3ab939f: je     0xb3ab93ba         ;*return
;... lines removed

在這種情況下,對變量l的getstatic引用都涉及來自內存的加載,即該值不能跨多個volatile讀取保存在寄存器中。 為確保有原子讀取,將數據從主存儲器讀取到MMX寄存器中movsd 0x6fb7b2f0(%ebp),%xmm0使讀取操作成為單一指令(從前面的例子中我們看到64位值通常需要兩個32位讀取一個32位系統)。

因此,易失性讀取的總體成本大致相當於內存負載,並且可以像L1緩存訪問一樣便宜。 但是,如果另一個內核正在寫入易失性變量,那麼緩存行將會失效,需要主內存或L3緩存訪問權限。 實際成本在很大程度上取決於CPU架構。 即使在Intel和AMD之間,緩存一致性協議也是不同的。


訪問volatile變量在許多方麵類似於在同步塊中包裝對普通變量的訪問。 例如,訪問volatile變量可以防止CPU在訪問之前和之後重新排序指令,這通常會減慢執行速度(儘管我不能說多少)。

更一般地說,在多處理器系統上,我看不出如何在沒有懲罰的情況下訪問volatile變量 - 必須有某種方法來確保處理器A上的寫入將與處理器B上的讀取同步。





volatile