JDK之僞共享False Sharing

    我瞭解僞分享是在看Disruptor源碼時開始的。html

1. @Contented註解

    JDK8中引入了@Contented,不過這個註解在sun包中,以下List-1java

    List-1數組

package sun.misc;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.TYPE})
public @interface Contended {
    String value() default "";
}

    這個註解只能在類屬性、類上使用。緩存

2. 我機器上CentOS的緩存行大小

    下面的Centos版本是Centos7 64位,運行在個人虛擬機上,可用的cpu核數是4,可用的運行內存是8192M,即8G。bash

    爲了高效地存取緩存, 不是簡單隨意地將單條數據寫入緩存的.  緩存是由緩存行組成的, 典型的一行是64字節,CentOS上能夠查看到,以下:架構

    List-2oracle

[dmj@localhost ~]$ cat /sys/devices/system/cpu/cpu0/cache/index0/coherency_line_size
64

    來看下cache/index3到cache/index0的size變化,以下:jvm

    List-3 從index3到index0是逐漸變小(index0表示越接近CPU,index3表示離CPU越遠)性能

[dmj@localhost ~]$ cat /sys/devices/system/cpu/cpu0/cache/index3/size
8192K
[dmj@localhost ~]$ cat /sys/devices/system/cpu/cpu0/cache/index2/size
256K
[dmj@localhost ~]$ cat /sys/devices/system/cpu/cpu0/cache/index1/size
32K
[dmj@localhost ~]$ cat /sys/devices/system/cpu/cpu0/cache/index0/size
32K

    來看下cache/index0到cache/index3的level變化,以下:測試

    List-4 index3的level大於index0的

[dmj@localhost ~]$ cat /sys/devices/system/cpu/cpu0/cache/index0/level
1
[dmj@localhost ~]$ cat /sys/devices/system/cpu/cpu0/cache/index1/level
1
[dmj@localhost ~]$ cat /sys/devices/system/cpu/cpu0/cache/index2/level
2
[dmj@localhost ~]$ cat /sys/devices/system/cpu/cpu0/cache/index3/level
3

    來看下cache/index0到cache/index3的coherency_line_size值變化

    List-5 值都是64

[dmj@localhost ~]$ cat /sys/devices/system/cpu/cpu0/cache/index0/coherency_line_size
64
[dmj@localhost ~]$ cat /sys/devices/system/cpu/cpu0/cache/index1/coherency_line_size
64
[dmj@localhost ~]$ cat /sys/devices/system/cpu/cpu0/cache/index2/coherency_line_size
64
[dmj@localhost ~]$ cat /sys/devices/system/cpu/cpu0/cache/index3/coherency_line_size
64

    來看下cache/index0到cache/index3的type

    List-6 index0是Data、index1是Instruction、index2和index3都是Unified

[dmj@localhost ~]$ cat /sys/devices/system/cpu/cpu0/cache/index0/type
Data
[dmj@localhost ~]$ cat /sys/devices/system/cpu/cpu0/cache/index1/type
Instruction
[dmj@localhost ~]$ cat /sys/devices/system/cpu/cpu0/cache/index2/type
Unified
[dmj@localhost ~]$ cat /sys/devices/system/cpu/cpu0/cache/index3/type
Unified

      根據上面的數據,我猜想個人CentOS7上的CPU和緩存架構以下:

                                                   圖1 猜想的我Centos上的CPU、緩存結構圖

    注:圖1僅僅是我猜想,極可能是錯誤的,建議讀者本身去查看本身Linux系統中上述的值。

    mac系統查看"L1 data cache line size"的命令以下,參考自google論壇:

    List-7 我mac系統上,獲得的值也是64

mjduan@mjduandeMacBook-Pro:/tmp % sysctl hw.cachelinesize
hw.cachelinesize: 64

    CentOS上查看"L1 data cache line size"的命令以下,參考自google論壇:

    List-8 我CentOS上,獲得的值也是64

[dmj@localhost ~]$ getconf LEVEL1_DCACHE_LINESIZE
64

 

    上面作的基本是鋪墊,由咱們本身驗證得緩存行是64bytes,怎麼肯定單位是bytes,參考國外博客,以後咱們進一步進入僞共享。我發現這篇51CTO博客講的挺好的,這裏就再也不重複描述。關鍵點在於:假設倆個獨立的變量x、y內存上位於同一個緩存行中,CPU0要對x進行操做,CPU1要對y進行操做。當CPU0加載換成到本身的L0緩存中,CPU1加載緩存行到本身的L0緩存中,以後CPU0修改本身L0緩存中的x,此時CPU1的L0緩存中的緩存行是失效了,因此此時CPU1不能對本身緩存行進行操做,CPU1不得不從新從L3或者主存加載該緩存行。因此CPU0和CPU1得不到並行執行。

    通過上述討論,引出一個問題,CPU1的L0中緩存行怎麼知道,CPU0該緩存行中的某些數據,這個要了解下MESI協議,能夠了解下這篇博客

3.怎麼避免僞分享

3.1 填充(不是很建議使用填充,用@Contented,能夠參考這個)

    "對於HotSpot JVM,全部對象都有兩個字長的對象頭。第一個字是由24位哈希碼和8位標誌位(如鎖的狀態或做爲鎖對象)組成的Mark Word。第二個字是對象所屬類的引用。若是是數組對象還須要一個額外的字來存儲數組的長度。每一個對象的起始地址都對齊於8字節以提升性能",這段話來自於51CTO開發頻道博客。這讓我想起來《深刻理解計算機》這本書中講過變量聲明順序對內存的優化。

    一般下給出的是填充緩存行,以下List-9。

    List-9 "public long p1, p2, p3, p4, p5, p6; // comment out"這行就是填充使用的

public final static class VolatileLong { 
        public volatile long value = 0L; 
        public long p1, p2, p3, p4, p5, p6; // comment out 
}

    可是這個要求開發人員對計算機底層和JVM內存有深的瞭解,要優化,可是也要慎重:

  •     沒有達到優化的效果,反過來讓程序代碼可讀性、可維護性變差。
  •     Java編譯器會不會在編譯時對代碼進行優化?好比編譯器在編譯期間將"public long p1, p2, p3, p4, p5, p6; "移除致使僞分享又發生,由於這幾個變量沒有被使用到。這個我想能夠經過Javap命令來查看,不瞭解Javap命令的同窗能夠去google/bing.com搜索下,這是java自帶的命令。
  •     JVM的JustInTime,即JIT,在Java代碼執行時會不會對代碼進行優化?它在執行期間檢測到"public long p1, p2, p3, p4, p5, p6; "這個變量沒有被使用到,因此將這些移除,使得僞分享又發生。Stackoverflow上有人使用openJDK的jol分析了,結果說"JIT can't figure that some fields are unused and doesn't optimize them out."。不過這個我沒有本身試驗過,後面有時間再去驗證。

    我的主觀的認爲,沒有被使用的局部變量頗有可能被優化,可是沒有被使用到的類屬性被優化的可能性不是很大,注意,這個僅僅是主觀的認爲,沒有試驗驗證過。

3.2 JDK的@Contented註解

    JDK中的@Contented註解,這個的做用我沒有仔細驗證過,且這個註解是在sun.misc包中的,這個在開頭給出了。Oracle的博客給出說這個註解能夠必定程度上減小False sharing的發生。

    注意通過本人驗證,JDK8上加上@Contented註解是不會生效的,除非加上List-10中的JVM參數。我是怎麼知道不加List-10的參數,@Contented不會生效的呢,請參考這裏,連接裏面的是個OpenJDK jol的例子,在運行是測試下加上和不加上List-10JVM參數的結果,運行結果能夠看到佔用內存bits位數的變化。

    List-10

-XX:-RestrictContended

    除了填充和@Contented外,頗有可能有其它方法,建議讀者多google/bing.com,也許能找到更充分的證據,最後是要本身驗證。

 

    最好是本身測試下,若是測試發現優化後確實效率高了,能夠運行在生產上,特別是注意測試數據量大的狀況和長時間運行的狀況。 

相關文章
相關標籤/搜索