成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

偽共享和緩存行

開發 開發工具
偽共享指的是在多個線程同時讀寫同一個緩存行的不同變量的時候,盡管這些變量之間沒有任何關系,但是在多個線程之間仍然需要同步,從而導致性能下降的情況。

[[196784]]

在計算機系統中,內存是以緩存行為單位存儲的,一個緩存行存儲字節的數量為2的倍數,在不同的機器上,緩存行大小為32字節到256字節不等,通常來說為64字節。偽共享指的是在多個線程同時讀寫同一個緩存行的不同變量的時候,盡管這些變量之間沒有任何關系,但是在多個線程之間仍然需要同步,從而導致性能下降的情況。在對稱多處理器結構的系統中,偽共享是影響性能的主要因素之一,由于很難通過走查代碼的方式定位偽共享的問題,因此,大家把偽共享稱為“性能殺手”。

為了通過增加線程數來達到計算能力的水平擴展,我們必須確保多個線程不能同時對一個變量或者緩存行進行讀寫。我們可以通過代碼走查的方式,定位多個線程讀寫一個變量的情況,但是,要想知道多個線程讀寫同一個緩存行的情況,我們必須先了解系統內存的組織形式,如下圖所示。

系統的緩存結構

從上圖看到,線程1在CPU核心1上讀寫變量X,同時線程2在CPU核心2上讀寫變量Y,不幸的是變量X和變量Y在同一個緩存行上,每一個線程為了對緩存行進行讀寫,都要競爭并獲得緩存行的讀寫權限,如果線程2在CPU核心2上獲得了對緩存行進行讀寫的權限,那么線程1必須刷新它的緩存后才能在核心1上獲得讀寫權限,這導致這個緩存行在不同的線程間多次通過L3緩存來交換最新的拷貝數據,這極大的影響了多核心CPU的性能。如果這些CPU核心在不同的插槽上,性能會變得更糟。

現在,我們學習JVM對象的內存模型。所有的Java對象都有8字節的對象頭,前四個字節用來保存對象的哈希碼和鎖的狀態,前3個字節用來存儲哈希碼,最后一個字節用來存儲鎖狀態,一旦對象上鎖,這4個字節都會被拿出對象外,并用指針進行鏈接。剩下4個字節用來存儲對象所屬類的引用。對于數組來講,還有一個保存數組大小的變量,為4字節。每一個對象的大小都會對齊到8字節的倍數,不夠8字節部分需要填充。為了保證效率,Java編譯器在編譯Java對象的時候,通過字段類型對Java對象的字段進行排序,如下表所示

因此,我們可以在任何字段之間通過填充長整型的變量把熱點變量隔離在不同的緩存行中,通過減少偽同步,在多核心CPU中能夠極大的提高效率。

下面,我們通過一個測試用例來證明我們的理論分析的正確性,參考下面的代碼段。

  1. package com.robert.concurrency.cacheline; 
  2.  
  3. /** 
  4.  *  
  5.  * @author:李艷鵬 
  6.  * @since:Jun 11, 2017 1:01:29 AM 
  7.  * @version: 1.0 
  8.  */ 
  9. public final class FalseSharingDemo { 
  10.  
  11.     // 測試用的線程數 
  12.     private final static int NUM_THREADS = 4; 
  13.  
  14.     // 測試的次數 
  15.     private final static int NUM_TEST_TIMES = 10; 
  16.  
  17.     // 無填充、無緩存行對齊的對象類 
  18.     static class PlainHotVariable { 
  19.  
  20.         public volatile long value = 0L; 
  21.     } 
  22.  
  23.     // 有填充、有緩存行對齊的對象類 
  24.     static final class AlignHotVariable extends PlainHotVariable { 
  25.  
  26.         public long p1, p2, p3, p4, p5, p6; 
  27.     } 
  28.  
  29.     static final class CompetitorThread extends Thread { 
  30.  
  31.         private final static long ITERATIONS = 500L * 1000L * 1000L; 
  32.  
  33.         private PlainHotVariable plainHotVariable; 
  34.  
  35.         public CompetitorThread(final PlainHotVariable plainHotVariable) { 
  36.             this.plainHotVariable = plainHotVariable; 
  37.         } 
  38.  
  39.         @Override 
  40.         public void run() { 
  41.             // 一個線程對一個變量進行大量的存取操作 
  42.             for (int i = 0; i < ITERATIONS; i++) { 
  43.                 plainHotVariable.value = i; 
  44.             } 
  45.  
  46.         } 
  47.  
  48.     } 
  49.  
  50.     public static long runOneTest(PlainHotVariable[] plainHotVariables) throws Exception { 
  51.         // 開啟多個線程進行測試 
  52.         CompetitorThread[] competitorThreads = new CompetitorThread[plainHotVariables.length]; 
  53.         for (int i = 0; i < plainHotVariables.length; i++) { 
  54.             competitorThreads[i] = new CompetitorThread(plainHotVariables[i]); 
  55.         } 
  56.  
  57.         final long start = System.nanoTime(); 
  58.         for (Thread t : competitorThreads) { 
  59.             t.start(); 
  60.         } 
  61.  
  62.         for (Thread t : competitorThreads) { 
  63.             t.join(); 
  64.         } 
  65.  
  66.         // 統計每次測試使用的時間 
  67.         return System.nanoTime() - start; 
  68.     } 
  69.  
  70.     public static boolean runOneCompare(int theadNum) throws Exception { 
  71.         PlainHotVariable[] plainHotVariables = new PlainHotVariable[theadNum]; 
  72.  
  73.         for (int i = 0; i < theadNum; i++) { 
  74.             plainHotVariables[i] = new PlainHotVariable(); 
  75.         } 
  76.  
  77.         // 進行無填充、無緩存行對齊的測試 
  78.         long t1 = runOneTest(plainHotVariables); 
  79.  
  80.         AlignHotVariable[] alignHotVariable = new AlignHotVariable[theadNum]; 
  81.  
  82.         for (int i = 0; i < NUM_THREADS; i++) { 
  83.             alignHotVariable[i] = new AlignHotVariable(); 
  84.         } 
  85.  
  86.         // 進行有填充、有緩存行對齊的測試 
  87.  
  88.         long t2 = runOneTest(alignHotVariable); 
  89.  
  90.         System.out.println("Plain: " + t1); 
  91.         System.out.println("Align: " + t2); 
  92.  
  93.         // 返回對比結果 
  94.         return t1 > t2; 
  95.     } 
  96.  
  97.     public static void runOneSuit(int threadsNum, int testNum) throws Exception { 
  98.         int expectedCount = 0; 
  99.         for (int i = 0; i < testNum; i++) { 
  100.             if (runOneCompare(threadsNum)) 
  101.                 expectedCount++; 
  102.         } 
  103.  
  104.         // 計算有填充、有緩存行對齊的測試場景下響應時間更短的情況的概率 
  105.         System.out.println("Radio (Plain < Align) : " + expectedCount * 100D / testNum + "%"); 
  106.     } 
  107.  
  108.     public static void main(String[] args) throws Exception { 
  109.         runOneSuit(NUM_THREADS, NUM_TEST_TIMES); 
  110.     } 

在上面的代碼示例中,我們做了10次測試,每次對不填充的變量和填充的變量進行大量讀寫所花費的時間對比來判斷偽同步對性能的影響。在每次對比中,我們首先創建了具有4個普通對象的數組,每個對象里包含一個長整形的變量,由于長整形占用8個字節,對象頭占用8個字節,每個對象占用16個字節,4個對象占用64個字節,因此,他們很有可能在同一個緩存行內。

  1. ... 
  2.  
  3.     // 無填充、無緩存行對齊的對象類 
  4.     static class PlainHotVariable { 
  5.  
  6.         public volatile long value = 0L; 
  7.     } 
  8.  
  9.     ... 
  10.  
  11.     PlainHotVariable[] plainHotVariables = new PlainHotVariable[theadNum]; 
  12.  
  13.     for (int i = 0; i < theadNum; i++) { 
  14.         plainHotVariables[i] = new PlainHotVariable(); 
  15.     } 
  16.     ... 

注意,這里value必須是volatile修飾的變量,這樣其他的線程才能看到它的變化。

接下來,我們創建了具有4個填充對象的數組,每個對象里包含一個長整形的變量,后面填充6個長整形的變量,由于長整形占用8個字節,對象頭占用8個字節,每個對象占用64個字節,4個對象占用4個64字節大小的空間,因此,他們每個對象正好與64字節對齊,會有效的消除偽競爭。

  1. ... 
  2.  
  3.     // 有填充、有緩存行對齊的對象類 
  4.     static final class AlignHotVariable extends PlainHotVariable { 
  5.  
  6.         public long p1, p2, p3, p4, p5, p6; 
  7.     } 
  8.  
  9.     ... 
  10.  
  11.     AlignHotVariable[] alignHotVariable = new AlignHotVariable[theadNum]; 
  12.  
  13.     for (int i = 0; i < NUM_THREADS; i++) { 
  14.         alignHotVariable[i] = new AlignHotVariable(); 
  15.     } 
  16.     ... 

對于上面創建的對象數組,我們開啟4個線程,每個線程對數組中的其中一個變量進行大量的存取操作,然后,對比測試結果如下。

1線程:

  1. Plain: 3880440094 
  2. Align: 3603752245 
  3. Plain: 3639901291 
  4. Align: 3633625092 
  5. Plain: 3623244143 
  6. Align: 3840919263 
  7. Plain: 3601311736 
  8. Align: 3695416688 
  9. Plain: 3837399466 
  10. Align: 3629233967 
  11. Plain: 3672411584 
  12. Align: 3622377013 
  13. Plain: 3678894140 
  14. Align: 3614962801 
  15. Plain: 3685449655 
  16. Align: 3578069018 
  17. Plain: 3650083667 
  18. Align: 4108272016 
  19. Plain: 3643323254 
  20. Align: 3840311867 
  21. Radio (Plain > Align) : 60.0% 

2線程

  1. Plain: 17403262079 
  2. Align: 3946343388 
  3. Plain: 3868304700 
  4. Align: 3650775431 
  5. Plain: 12111598939 
  6. Align: 4224862180 
  7. Plain: 4805070043 
  8. Align: 4130750299 
  9. Plain: 15889926613 
  10. Align: 3901238050 
  11. Plain: 12059354004 
  12. Align: 3771834390 
  13. Plain: 16744207113 
  14. Align: 4433367085 
  15. Plain: 4090413088 
  16. Align: 3834753740 
  17. Plain: 11791092554 
  18. Align: 3952127226 
  19. Plain: 12125857773 
  20. Align: 4140062817 
  21. Radio (Plain > Align) : 100.0% 

4線程:

  1. Plain: 12714212746 
  2. Align: 7462938088 
  3. Plain: 12865714317 
  4. Align: 6962498317 
  5. Plain: 18767257391 
  6. Align: 7632201194 
  7. Plain: 12730329600 
  8. Align: 6955849781 
  9. Plain: 12246997913 
  10. Align: 7457147789 
  11. Plain: 17341965313 
  12. Align: 7333927073 
  13. Plain: 19363865296 
  14. Align: 7323193058 
  15. Plain: 12201435415 
  16. Align: 7279922233 
  17. Plain: 12810166367 
  18. Align: 7613635297 
  19. Plain: 19235104612 
  20. Align: 7398148996 
  21. Radio (Plain > Align) : 100.0% 

從上面的測試結果中可以看到,使用填充的數組進行測試,花費的時間普遍小于使用不填充的數組進行測試的情況,并且隨著線程數的增加,使用不填充的數組的場景性能隨之下降,可伸縮性也變得越來越弱,見下圖所示。

無填充和有填充對比圖標

盡管我們并不精確的知道系統如何分配我們的對象,但是,我們的測試結果驗證了我們的理論分析的正確性。

實際上,著名的無鎖隊列Disruptor通過解決偽競爭的問題來提高效率,它通過在RingBuffer的游標和BatchEventProcessor的序列變量后填充變量,使之與64字節大小的緩存行對齊來解決偽競爭的問題。

上面我們看到緩存行的機制在多線程環境下會產生偽同步,現在,我們學習另外一個由于緩存行影響性能的示例,代碼如下所示。

  1. package com.robert.concurrency.cacheline; 
  2.  
  3. /** 
  4.  *  
  5.  * @author:李艷鵬 
  6.  * @since:Jun 11, 2017 1:01:29 AM 
  7.  * @version: 1.0 
  8.  */ 
  9.  
  10. public final class CacheLineDemo { 
  11.  
  12.     // 緩存行的大小為64個字節,即為8個長整形 
  13.     private final static int CACHE_LINE_LONG_NUM = 8; 
  14.  
  15.     // 用于測試的緩存行的數量 
  16.     private final static int LINE_NUM = 1024 * 1024; 
  17.  
  18.     // 一次測試的次數 
  19.     private final static int NUM_TEST_TIMES = 10; 
  20.  
  21.     // 構造能夠填充LINE_NUM個緩存行的數組 
  22.     private static final long[] values = new long[CACHE_LINE_LONG_NUM * LINE_NUM]; 
  23.  
  24.     public static long runOneTestWithAlign() { 
  25.  
  26.         final long start = System.nanoTime(); 
  27.  
  28.         // 進行順序讀取測試,期待在存取每個緩存行的第一個長整形變量的時候系統自動緩存整個緩存行,本行的后續存取都會命中緩存 
  29.         for (int i = 0; i < CACHE_LINE_LONG_NUM * LINE_NUM; i++) 
  30.             values[i] = i; 
  31.  
  32.         return System.nanoTime() - start; 
  33.  
  34.     } 
  35.  
  36.     public static long runOneTestWithoutAlign() { 
  37.         final long start = System.nanoTime(); 
  38.  
  39.         // 按照緩存行的步長進行跳躍讀取測試,期待每次讀取一行中的一個元素,每次讀取都不會命中緩存 
  40.         for (int i = 0; i < CACHE_LINE_LONG_NUM; i++) 
  41.             for (int j = 0; j < LINE_NUM; j++) 
  42.                 values[j * CACHE_LINE_LONG_NUM + i] = i * j; 
  43.  
  44.         return System.nanoTime() - start; 
  45.     } 
  46.  
  47.     public static boolean runOneCompare() { 
  48.         long t1 = runOneTestWithAlign(); 
  49.         long t2 = runOneTestWithoutAlign(); 
  50.  
  51.         System.out.println("Sequential: " + t1); 
  52.         System.out.println("      Leap: " + t2); 
  53.  
  54.         return t1 < t2; 
  55.     } 
  56.  
  57.     public static void runOneSuit(int testNum) throws Exception { 
  58.         int expectedCount = 0; 
  59.         for (int i = 0; i < testNum; i++) { 
  60.             if (runOneCompare()) 
  61.                 expectedCount++; 
  62.         } 
  63.  
  64.         // 計算順序訪問數組的測試場景下,響應時間更短的情況的概率 
  65.  
  66.         System.out.println("Radio (Sequential < Leap): " + expectedCount * 100D / testNum + "%"); 
  67.     } 
  68.  
  69.     public static void main(String[] args) throws Exception { 
  70.         runOneSuit(NUM_TEST_TIMES); 
  71.     } 

在上面的示例中,我們創建了1024 1024 8個長整形數組,首先,我們順序訪問每一個長整形,按照前面我們對緩存行的分析,每8個長整形占用一個緩存行,那么也就是我們存取8個長整形才需要去L3緩存交換一次數據,大大的提高了緩存的使用效率。然后,我們換了一種方式進行測試,每次跳躍性的訪問數組,一次以一行為步長跳躍,我們期待每次訪問一個元素操作系統需要從L3緩存取數據,結果如下所示:

  1. Sequential: 11092440 
  2.       Leap: 66234827 
  3. Sequential: 9961470 
  4.       Leap: 62903705 
  5. Sequential: 7785285 
  6.       Leap: 64447613 
  7. Sequential: 7981995 
  8.       Leap: 73487063 
  9. Sequential: 8779595 
  10.       Leap: 74127379 
  11. Sequential: 10012716 
  12.       Leap: 67089382 
  13. Sequential: 8437842 
  14.       Leap: 79442009 
  15. Sequential: 13377366 
  16.       Leap: 80074056 
  17. Sequential: 11428147 
  18.       Leap: 81245364 
  19. Sequential: 9514993 
  20.       Leap: 69569712 
  21. Radio (Sequential < Leap): 100.0% 

我們從上面的結果中分析得到,順序訪問的速度每次都高于跳躍訪問的速度,驗證了我們前面對緩存行的理論分析。

這里我們看到,我們需要對JVM的實現機制以及操作系統內核有所了解,才能找到系統性能的瓶頸,最終提高系統的性能,進一步提高系統的用戶友好性。

點擊《偽共享和緩存行》閱讀原文。

【本文為51CTO專欄作者“李艷鵬”的原創稿件,轉載可通過作者簡書號(李艷鵬)或51CTO專欄獲取聯系】

戳這里,看該作者更多好文

責任編輯:武曉燕 來源: 51CTO專欄
相關推薦

2023-12-26 10:08:57

緩存偽共享修飾結構體

2019-12-17 14:24:11

CPU緩存偽共享

2012-12-17 14:54:55

算法緩存Java

2019-11-05 14:24:31

緩存雪崩框架

2021-08-05 16:10:03

進程緩存緩存服務Java

2021-11-30 10:58:52

算法緩存技術

2022-12-12 08:39:09

CPUCache偽共享

2021-12-25 22:28:27

緩存穿透緩存擊穿緩存雪崩

2022-01-17 14:24:09

共享字節面試

2021-11-18 08:55:49

共享CPU內存

2011-08-05 15:51:44

MySQL數據庫緩存

2021-01-20 05:33:03

緩存ReadWriteLo高并發

2017-08-23 13:21:31

2025-03-03 07:00:00

2013-06-14 10:12:22

共享并行

2023-08-30 10:28:02

LRU鏈表區域

2023-08-31 13:36:00

系統預讀失效

2021-03-01 11:53:15

面試偽共享CPU

2022-02-02 21:50:25

底層偽共享CPU

2024-11-05 13:50:12

點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 国产成人精品免费视频大全最热 | 国产综合精品 | 久久av一区二区三区 | 精品一区二区三区入口 | 欧美一级电影免费 | 一级国产精品一级国产精品片 | 久久com | 一级高清视频 | 成人高清在线视频 | 在线观看www | 麻豆一区二区三区精品视频 | 日韩一区二区三区在线视频 | 久久综合九色综合欧美狠狠 | 国产探花在线精品一区二区 | 久久国产精品一区二区三区 | 国产中文字幕在线观看 | 欧美一级在线免费 | 日本精品一区二区 | 欧美精品a∨在线观看不卡 国产精品久久国产精品 | 国产免费一区 | 日日网| 亚洲精品电影网在线观看 | 中国91av| 成人二区三区 | 久久精品亚洲 | 精品视频一区二区三区在线观看 | 夜夜骑综合 | 国产精品一区二 | 伊人伊成久久人综合网站 | 国产主播第一页 | 欧美一级大黄 | 国产乱码精品一区二区三区忘忧草 | 国产精品免费av | 欧美最猛性xxxxx亚洲精品 | 成人影院免费视频 | 在线免费观看a级片 | 国产精品久久久久久久久久三级 | 久久最新精品视频 | 一级特黄a大片 | 亚洲第一视频网 | 激情国产视频 |