日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程语言 > java >内容正文

java

伪共享和缓存行填充,Java并发编程还能这么优化!

發(fā)布時(shí)間:2023/12/19 java 21 豆豆
生活随笔 收集整理的這篇文章主要介紹了 伪共享和缓存行填充,Java并发编程还能这么优化! 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

前言

關(guān)于偽共享的文章已經(jīng)很多了,對(duì)于多線程編程來說,特別是多線程處理列表和數(shù)組的時(shí)候,要非常注意偽共享的問題。否則不僅無法發(fā)揮多線程的優(yōu)勢(shì),還可能比單線程性能還差。隨著JAVA版本的更新,再各個(gè)版本上減少偽共享的做法都有區(qū)別,一不小心代碼可能就失效了,要注意進(jìn)行測(cè)試。這篇文章總結(jié)一下。

什么是偽共享
關(guān)于偽共享講解最清楚的是這篇文章:http://developer.51cto.com/art/201306/398232.htm,我這里就直接摘抄其對(duì)偽共享的解釋:

緩存系統(tǒng)中是以緩存行(cache line)為單位存儲(chǔ)的。緩存行是2的整數(shù)冪個(gè)連續(xù)字節(jié),一般為32-256個(gè)字節(jié)。最常見的緩存行大小是64個(gè)字節(jié)。當(dāng)多線程修改互相獨(dú)立的變量時(shí),如果這些變量共享同一個(gè)緩存行,就會(huì)無意中影響彼此的性能,這就是偽共享。緩存行上的寫競(jìng)爭(zhēng)是運(yùn)行在SMP系統(tǒng)中并行線程實(shí)現(xiàn)可伸縮性最重要的限制因素。有人將偽共享描述成無聲的性能殺手,因?yàn)閺拇a中很難看清楚是否會(huì)出現(xiàn)偽共享。

為了讓可伸縮性與線程數(shù)呈線性關(guān)系,就必須確保不會(huì)有兩個(gè)線程往同一個(gè)變量或緩存行中寫。兩個(gè)線程寫同一個(gè)變量可以在代碼中發(fā)現(xiàn)。為了確定互相獨(dú)立的變量是否共享了同一個(gè)緩存行,就需要了解內(nèi)存布局,或找個(gè)工具告訴我們。Intel VTune就是這樣一個(gè)分析工具。本文中我將解釋Java對(duì)象的內(nèi)存布局以及我們?cè)撊绾翁畛渚彺嫘幸员苊鈧喂蚕怼?/p>



圖1說明了偽共享的問題。在核心1上運(yùn)行的線程想更新變量X,同時(shí)核心2上的線程想要更新變量Y。不幸的是,這兩個(gè)變量在同一個(gè)緩存行中。每個(gè)線程都要去競(jìng)爭(zhēng)緩存行的所有權(quán)來更新變量。如果核心1獲得了所有權(quán),緩存子系統(tǒng)將會(huì)使核心2中對(duì)應(yīng)的緩存行失效。當(dāng)核心2獲得了所有權(quán)然后執(zhí)行更新操作,核心1就要使自己對(duì)應(yīng)的緩存行失效。這會(huì)來來回回的經(jīng)過L3緩存,大大影響了性能。如果互相競(jìng)爭(zhēng)的核心位于不同的插槽,就要額外橫跨插槽連接,問題可能更加嚴(yán)重。

JAVA 6下的方案

解決偽共享的辦法是使用緩存行填充,使一個(gè)對(duì)象占用的內(nèi)存大小剛好為64bytes或它的整數(shù)倍,這樣就保證了一個(gè)緩存行里不會(huì)有多個(gè)對(duì)象。這篇文章http://developer.51cto.com/art/201306/398232.htm提供了緩存行填充的例子:

public final class FalseSharing implements Runnable { public final static int NUM_THREADS = 4; // change public final static long ITERATIONS = 500L * 1000L * 1000L; private final int arrayIndex; private static VolatileLong[] longs = new VolatileLong[NUM_THREADS]; static { for (int i = 0; i < longs.length; i++) { longs[i] = new VolatileLong(); } } public FalseSharing(final int arrayIndex) { this.arrayIndex = arrayIndex; } public static void main(final String[] args) throws Exception { final long start = System.nanoTime(); runTest(); System.out.println("duration = " + (System.nanoTime() - start)); } private static void runTest() throws InterruptedException { Thread[] threads = new Thread[NUM_THREADS]; for (int i = 0; i < threads.length; i++) { threads[i] = new Thread(new FalseSharing(i)); } for (Thread t : threads) { t.start(); } for (Thread t : threads) { t.join(); } } public void run() { long i = ITERATIONS + 1; while (0 != --i) { longs[arrayIndex].value = i; } } public final static class VolatileLong { public volatile long value = 0L; public long p1, p2, p3, p4, p5, p6; // comment out } }

VolatileLong通過填充一些無用的字段p1,p2,p3,p4,p5,p6,再考慮到對(duì)象頭也占用8bit, 剛好把對(duì)象占用的內(nèi)存擴(kuò)展到剛好占64bytes(或者64bytes的整數(shù)倍)。這樣就避免了一個(gè)緩存行中加載多個(gè)對(duì)象。但這個(gè)方法現(xiàn)在只能適應(yīng)JAVA6 及以前的版本了。

(注:如果我們的填充使對(duì)象size大于64bytes,比如多填充16bytes– public long p1, p2, p3, p4, p5, p6, p7, p8;。理論上同樣應(yīng)該避免偽共享問題,但事實(shí)是這樣的話執(zhí)行速度同樣慢幾倍,只比沒有使用填充好一些而已。還沒有理解其原因。所以測(cè)試下來,必須是64bytes的整數(shù)倍)

JAVA 7下的方案
上面這個(gè)例子在JAVA 7下已經(jīng)不適用了。因?yàn)镴AVA 7會(huì)優(yōu)化掉無用的字段,可以參考:http://ifeve.com/false-shareing-java-7-cn/。

因此,JAVA 7下做緩存行填充更麻煩了,需要使用繼承的辦法來避免填充被優(yōu)化掉,這篇文章http://ifeve.com/false-shareing-java-7-cn/里的例子我覺得不是很好,于是我自己做了一些優(yōu)化,使其更通用:

public final class FalseSharing implements Runnable { public static int NUM_THREADS = 4; // change public final static long ITERATIONS = 500L * 1000L * 1000L; private final int arrayIndex; private static VolatileLong[] longs; public FalseSharing(final int arrayIndex) { this.arrayIndex = arrayIndex; } public static void main(final String[] args) throws Exception { Thread.sleep(10000); System.out.println("starting...."); if (args.length == 1) { NUM_THREADS = Integer.parseInt(args[0]); } longs = new VolatileLong[NUM_THREADS]; for (int i = 0; i < longs.length; i++) { longs[i] = new VolatileLong(); } final long start = System.nanoTime(); runTest(); System.out.println("duration = " + (System.nanoTime() - start)); } private static void runTest() throws InterruptedException { Thread[] threads = new Thread[NUM_THREADS]; for (int i = 0; i < threads.length; i++) { threads[i] = new Thread(new FalseSharing(i)); } for (Thread t : threads) { t.start(); } for (Thread t : threads) { t.join(); } } public void run() { long i = ITERATIONS + 1; while (0 != --i) { longs[arrayIndex].value = i; } } }public class VolatileLongPadding {public volatile long p1, p2, p3, p4, p5, p6; // 注釋 }public class VolatileLong extends VolatileLongPadding {public volatile long value = 0L; }

把padding放在基類里面,可以避免優(yōu)化。(這好像沒有什么道理好講的,JAVA7的內(nèi)存優(yōu)化算法問題,能繞則繞)。不過,這種辦法怎么看都有點(diǎn)煩,借用另外一個(gè)博主的話:做個(gè)java程序員真難。

JAVA 8下的方案
在JAVA 8中,緩存行填充終于被JAVA原生支持了。JAVA 8中添加了一個(gè)@Contended的注解,添加這個(gè)的注解,將會(huì)在自動(dòng)進(jìn)行緩存行填充。以上的例子可以改為:

public final class FalseSharing implements Runnable { public static int NUM_THREADS = 4; // change public final static long ITERATIONS = 500L * 1000L * 1000L; private final int arrayIndex; private static VolatileLong[] longs; public FalseSharing(final int arrayIndex) { this.arrayIndex = arrayIndex; } public static void main(final String[] args) throws Exception { Thread.sleep(10000); System.out.println("starting...."); if (args.length == 1) { NUM_THREADS = Integer.parseInt(args[0]); } longs = new VolatileLong[NUM_THREADS]; for (int i = 0; i < longs.length; i++) { longs[i] = new VolatileLong(); } final long start = System.nanoTime(); runTest(); System.out.println("duration = " + (System.nanoTime() - start)); } private static void runTest() throws InterruptedException { Thread[] threads = new Thread[NUM_THREADS]; for (int i = 0; i < threads.length; i++) { threads[i] = new Thread(new FalseSharing(i)); } for (Thread t : threads) { t.start(); } for (Thread t : threads) { t.join(); } } public void run() { long i = ITERATIONS + 1; while (0 != --i) { longs[arrayIndex].value = i; } } }@Contended public class VolatileLong {public volatile long value = 0L; }

執(zhí)行時(shí),必須加上虛擬機(jī)參數(shù)-XX:-RestrictContended,@Contended注釋才會(huì)生效。很多文章把這個(gè)漏掉了,那樣的話實(shí)際上就沒有起作用。

@Contended注釋還可以添加在字段上,今后再寫文章詳細(xì)介紹它的用法。

(后記:以上代碼基于32位JDK測(cè)試,64位JDK下,對(duì)象頭大小不同,有空再測(cè)試一下)

參考

http://mechanical-sympathy.blogspot.com/2011/07/false-sharing.html

http://mechanical-sympathy.blogspot.hk/2011/08/false-sharing-java-7.html

http://robsjava.blogspot.com/2014/03/what-is-false-sharing.html

原文發(fā)布時(shí)間為:2018-07-12
本文作者:Binhua
本文來自云棲社區(qū)合作伙伴“Java架構(gòu)沉思錄”,了解相關(guān)信息可以關(guān)注“Java架構(gòu)沉思錄”。

總結(jié)

以上是生活随笔為你收集整理的伪共享和缓存行填充,Java并发编程还能这么优化!的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。