僞共享,無聲的性能殺手

CPU緩存設計與工做機制

  在介紹僞共享以前,先了解一下如今經常使用的CPU架構的緩存設計,以下圖:咱們常見的CPU架構裏緩存都是L1 L2 L3多級緩存,其中L1 L2 在每一個CPU內核中(每一個Core都有L1 L2緩存), L3獨立與全部核以外java


圖片


cpu核心讀取數據老是從L1開始,L1不存在,再讀取L2,L2中數據不存在再讀取L3,L3數據不存在再讀從內存中讀取。根據網上的一些資料顯示,CPU在L1緩存內讀取的速度是小於1ns,L2緩存在1-2ns L3緩存在10ns左右,而從內存讀取要100ns左右,而咱們的緩存存儲數據都是經過緩存行來進行存儲的,緩存行的大小通常都是2的倍數區間在32字節到256字節間,主流CPU的緩存行大小爲64字節數組


圖片


上圖是一個經典的多線程環境讀取同一個緩存行裏的數據的示例解析:X Y 變量被CPU緩存讀取放到同一個緩存行裏,Core1 核心的線程想更改X的值,Core2核心的線程想更改Y的值,由於CPU在對數據進行讀取的時候遵循緩存一致性來解決高速緩存的數據不一致問題,因此每次Core1 核心線程對X的修改,就會同步一次Core2核心的L1 L2緩存裏同一緩存行裏的X值,每次Core2核心線程對Y的修改,也都要統一緩存行裏的Y值,這樣就形成了嚴重的性能消耗,這就是所謂的僞共享
如何解決僞共享咱們既然知道了CPU緩存行裏的多個數據在多線程環境下會形成僞共享,那解決方法就是使用緩存行填充方案,不要把存在競爭關係的數據 放到同一個緩存行裏。不放在同一個緩存行裏,多線程競爭狀況下,cpu每一個Core核心的L1 L2緩存就不會強制緩存同步形成性能損耗
JAVA解決方案緩存

  • java7

例如,多線程環境下對TestLong裏的vaue字段進行更新public class TestLong{    public volatile long p1, p2, p3, p4, p5, p6; // 緩存行填充,自定義6個long類型變量    public volatile long value = 0L;      .......}緩存行填充方案:64位系統Java 程序的對象頭固定佔 16個 字節(32位系統是8個字節),因此咱們只須要填 6 個無用的Long類型補上6*8=48字節湊滿64位,不過這裏有個兼容問題,這套代碼也就能針對64位系統中運行。多線程

  • java8 

jdk8中提供了@Contended註解來解決僞共享問題。該註解會自動根據運行代碼平臺的cpu的緩存行大小進行設置緩存行填充方案固然:要使用@Contended 註解 必須增長JVM參數 -XX:-RestrictContended測試代碼(base:Java8)架構

package com.fqh.review.base.cacheLine;import sun.misc.Contended;/** * @author fqh * @Description: Java8緩存行填充測試類--比較測試須要放開LongArr變量的Contended註解增長-   XX:-RestrictContended JVM參數  * @date 2020/7/27下午5:44 */public class CacheLinePadding {
//@Contendedpublic static volatile long[] longArr=new long[2];
public static void main(String[] argus) throws InterruptedException {    Thread t1=new Thread(() -> {for(long i=0;i<5000000000L;i++){//50億次        longArr[0]=i;      }    });    Thread t2=new Thread(() -> {for(long i=0;i<5000000000L;i++){        longArr[1]=i;      }    });long startTime = System.nanoTime();    t1.start();    t2.start();    t1.join();    t2.join();    System.out.println((System.nanoTime()-startTime)/100_0000);//運行時間秒  }}

我電腦配置:
處理器名稱:       Intel Core i7處理器速度:       2.7 GHz處理器數目:       1核總數:          4L2 緩存(每一個核):  256 KBL3 緩存:       8 MB內存:       16 GB

通過測試:50億次對數組 longArr數組元素寫入
  • 未開啓Contended註解填充緩存行的,運行時間在22 23左右
  • 開啓Contended註解增長jvm參數XX:-RestrictContended運行時間在20秒之內。

工做中的實際使用app

  • 在對應類或者屬性字段添加註解 @Contended
  • 其次運行的時候須要設置VM參數爲 -XX:-RestrictContended
  • 若是須要設置cache line的寬度,能夠經過 -XX:-RestrictContended -XX:ContendedPaddingWidth=256 設置生效
相關文章
相關標籤/搜索