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

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 人文社科 > 生活经验 >内容正文

生活经验

FastThreadLocal吞吐量居然是ThreadLocal的3倍

發(fā)布時間:2023/11/27 生活经验 43 豆豆
生活随笔 收集整理的這篇文章主要介紹了 FastThreadLocal吞吐量居然是ThreadLocal的3倍 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

目前關(guān)于FastThreadLocal的很多文章都有點老有點過時了(本文將澄清幾個誤區(qū)),很多文章關(guān)于FastThreadLocal介紹的也不全,希望本篇文章可以帶你徹底理解FastThreadLocal!!!

FastThreadLocal是Netty提供的,在池化內(nèi)存分配等都有涉及到!

關(guān)于FastThreadLocal,零度準(zhǔn)備從這幾個方面進(jìn)行講解:

  • FastThreadLocal的使用。
  • FastThreadLocal并不是什么情況都快,你要用對才會快。
  • FastThreadLocal利用字節(jié)填充來解決偽共享問題。
  • FastThreadLocal比ThreadLocal快,并不是空間換時間。
  • FastThreadLocal不在使用ObjectCleaner處理泄漏,必要的時候建議重寫onRemoval方法。
  • FastThreadLocal為什么快?

FastThreadLocal的使用

FastThreadLocal用法上兼容ThreadLocal

FastThreadLocal使用示例代碼截圖:

?

?

?

代碼運行結(jié)果:

?

?

?

我們在回顧下之前的ThreadLocal的最佳實踐做法:

try {
// 其它業(yè)務(wù)邏輯
} finally {
threadLocal對象.remove();
}

?

備注:通過上面的例子,我們發(fā)現(xiàn)FastThreadLocal和ThreadLocal在用法上面基本差不多,沒有什么特別區(qū)別,個人認(rèn)為,這就是FastThreadLocal成功的地方,它就是要讓用戶用起來和ThreadLocal沒啥區(qū)別,要兼容!

使用FastThreadLocal居然不用像ThreadLocal那樣先try ………………… 之后finally進(jìn)行threadLocal對象.remove();

由于構(gòu)造FastThreadLocalThread的時候,通過FastThreadLocalRunnable對Runnable對象進(jìn)行了包裝:

FastThreadLocalRunnable.wrap(target)
從而構(gòu)造了FastThreadLocalRunnable對象。

?

?

?

FastThreadLocalRunnable在執(zhí)行完之后都會調(diào)用FastThreadLocal.removeAll();

?

?

?

備注: FastThreadLocal不在使用ObjectCleaner處理泄漏,必要的時候建議重寫onRemoval方法。關(guān)于這塊將在本文后面進(jìn)行介紹,這樣是很多網(wǎng)上資料比較老的原因,這塊已經(jīng)去掉了。
如果是普通線程,還是應(yīng)該最佳實踐:
finally {
fastThreadLocal對象.removeAll();
}
注意: 如果使用FastThreadLocal就不要使用普通線程,而應(yīng)該構(gòu)建FastThreadLocalThread,關(guān)于為什么這樣,關(guān)于這塊將在本文后面進(jìn)行介紹:FastThreadLocal并不是什么情況都快,你要用對才會快。

FastThreadLocal并不是什么情況都快,你要用對才會快

首先看看netty關(guān)于這塊的測試用例:

代碼路徑:https://github.com/netty/netty/blob/4.1/microbench/src/main/java/io/netty/microbench/concurrent/FastThreadLocalFastPathBenchmark.java

?

備注:?在我本地進(jìn)行測試,FastThreadLocal的吞吐量是jdkThreadLocal的3倍左右。機(jī)器不一樣,可能效果也不一樣,大家可以自己試試,反正就是快了不少。

關(guān)于ThreadLocal,之前的這篇:手撕面試題ThreadLocal!!!已經(jīng)詳細(xì)介紹了。

FastThreadLocal并不是什么情況都快,你要用對才會快!!!

注意:?使用FastThreadLocalThread線程才會快,如果是普通線程還更慢!

注意:?使用FastThreadLocalThread線程才會快,如果是普通線程還更慢!

注意:?使用FastThreadLocalThread線程才會快,如果是普通線程還更慢!

?

?

?

netty的測試目錄下面有2個類:

  • FastThreadLocalFastPathBenchmark
  • FastThreadLocalSlowPathBenchmark

路徑:https://github.com/netty/netty/blob/4.1/microbench/src/main/java/io/netty/microbench/concurrent/

FastThreadLocalFastPathBenchmark測試結(jié)果:是ThreadLocal的吞吐量的3倍左右。

?

?

?

FastThreadLocalSlowPathBenchmark測試結(jié)果:比ThreadLocal的吞吐量還低。

?

?

?

測試結(jié)論:使用FastThreadLocalThread線程操作FastThreadLocal才會快,如果是普通線程還更慢!

?

注釋里面給出了三點:

  • FastThreadLocal操作元素的時候,使用常量下標(biāo)在數(shù)組中進(jìn)行定位元素來替代ThreadLocal通過哈希和哈希表,這個改動特別在頻繁使用的時候,效果更加顯著!
  • 想要利用上面的特征,線程必須是FastThreadLocalThread或者其子類,默認(rèn)DefaultThreadFactory都是使用FastThreadLocalThread的
  • 只用在FastThreadLocalThread或者子類的線程使用FastThreadLocal才會更快,因為FastThreadLocalThread 定義了屬性threadLocalMap類型是InternalThreadLocalMap。如果普通線程會借助ThreadLocal。

我們看看NioEventLoopGroup細(xì)節(jié):

?

?

?

?

?

?

?

看到這里,和剛剛我們看到的注釋內(nèi)容一致的,是使用FastThreadLocalThread的。

netty里面使用FastThreadLocal的舉例常用的:

池化內(nèi)存分配:

?

?

?

會使用到Recycler

?

?

?

而Recycler也使用了FastThreadLocal

?

?

?

我們再看看看測試類:

?

?

?

備注:我們會發(fā)現(xiàn)FastThreadLocalFastPathBenchmark里面的線程是FastThreadLocal。

?

?

?

備注:我們會發(fā)現(xiàn)FastThreadLocalSlowPathBenchmark里面的線程不是FastThreadLocal。

FastThreadLocal只有被的線程是FastThreadLocalThread或者其子類使用的時候才會更快,吞吐量我這邊測試的效果大概3倍左右,但是如果是普通線程操作FastThreadLocal其吞吐量比ThreadLocal還差!

FastThreadLocal利用字節(jié)填充來解決偽共享問題

關(guān)于CPU 緩存 內(nèi)容來源于美團(tuán):https://tech.meituan.com/2016/11/18/disruptor.html

下圖是計算的基本結(jié)構(gòu)。L1、L2、L3分別表示一級緩存、二級緩存、三級緩存,越靠近CPU的緩存,速度越快,容量也越小。所以L1緩存很小但很快,并且緊靠著在使用它的CPU內(nèi)核;L2大一些,也慢一些,并且仍然只能被一個單獨的CPU核使用;L3更大、更慢,并且被單個插槽上的所有CPU核共享;最后是主存,由全部插槽上的所有CPU核共享。

?

?

?

當(dāng)CPU執(zhí)行運算的時候,它先去L1查找所需的數(shù)據(jù)、再去L2、然后是L3,如果最后這些緩存中都沒有,所需的數(shù)據(jù)就要去主內(nèi)存拿。走得越遠(yuǎn),運算耗費的時間就越長。所以如果你在做一些很頻繁的事,你要盡量確保數(shù)據(jù)在L1緩存中。

另外,線程之間共享一份數(shù)據(jù)的時候,需要一個線程把數(shù)據(jù)寫回主存,而另一個線程訪問主存中相應(yīng)的數(shù)據(jù)。

下面是從CPU訪問不同層級數(shù)據(jù)的時間概念:

?

?

?

可見CPU讀取主存中的數(shù)據(jù)會比從L1中讀取慢了近2個數(shù)量級。

緩存行

Cache是由很多個cache line組成的。每個cache line通常是64字節(jié),并且它有效地引用主內(nèi)存中的一塊兒地址。一個Java的long類型變量是8字節(jié),因此在一個緩存行中可以存8個long類型的變量。

CPU每次從主存中拉取數(shù)據(jù)時,會把相鄰的數(shù)據(jù)也存入同一個cache line。

在訪問一個long數(shù)組的時候,如果數(shù)組中的一個值被加載到緩存中,它會自動加載另外7個。因此你能非常快的遍歷這個數(shù)組。事實上,你可以非常快速的遍歷在連續(xù)內(nèi)存塊中分配的任意數(shù)據(jù)結(jié)構(gòu)。

偽共享

由于多個線程同時操作同一緩存行的不同變量,但是這些變量之間卻沒有啥關(guān)聯(lián),但是每次修改,都會導(dǎo)致緩存的數(shù)據(jù)變成無效,從而明明沒有任何修改的內(nèi)容,還是需要去主存中讀(CPU讀取主存中的數(shù)據(jù)會比從L1中讀取慢了近2個數(shù)量級)但是其實這塊內(nèi)容并沒有任何變化,由于緩存的最小單位是一個緩存行,這就是偽共享。

如果讓多線程頻繁操作的并且沒有關(guān)系的變量在不同的緩存行中,那么就不會因為緩存行的問題導(dǎo)致沒有關(guān)系的變量的修改去影響另外沒有修改的變量去讀主存了(那么從L1中取是從主存取快2個數(shù)量級的)那么性能就會好很多很多。

有偽共享 和沒有的情況的測試效果

代碼路徑:https://github.com/jiangxinlingdu/nettydemo

nettydemo

?

?

?

?

?

利用字節(jié)填充來解決偽共享,從而速度快了3倍左右。

FastThreadLocal使用字節(jié)填充解決偽共享

之前介紹ThreadLocal的時候,說過ThreadLocal是用在多線程場景下,那么FastThreadLocal也是用在多線程場景,大家可以看下這篇:手撕面試題ThreadLocal!!!,所以FastThreadLocal需要解決偽共享問題,FastThreadLocal使用字節(jié)填充解決偽共享。

?

?

?

?

?

這個是我自己手算的,通過手算太麻煩,推薦一個工具JOL。

http://openjdk.java.net/projects/code-tools/jol/

?

?

?

推薦IDEA插件:https://plugins.jetbrains.com/plugin/10953-jol-java-object-layout

?

?

?

代碼路徑:https://github.com/jiangxinlingdu/nettydemo

nettydemo

?

?

?

通過這個工具算起來就很容易了,如果以后有類似的需要看的,不用手一個一個算了。

FastThreadLocal被FastThreadLocalThread進(jìn)行讀寫的時候也可能利用到緩存行

?

?

?

并且由于當(dāng)線程是FastThreadLocalThread的時候操作FastThreadLocal是通過indexedVariables數(shù)組進(jìn)行存儲數(shù)據(jù)的的,每個FastThreadLocal有一個常量下標(biāo),通過下標(biāo)直接定位數(shù)組進(jìn)行讀寫操作,當(dāng)有很多FastThreadLocal的時候,也可以利用緩存行,比如一次indexedVariables數(shù)組第3個位置數(shù)據(jù),由于緩存的最小單位是緩存行,順便把后面的4、5、6等也緩存了,下次剛剛好另外FastThreadLocal下標(biāo)就是5的時候,進(jìn)行讀取的時候就直接走緩存了,比走主存可能快2個數(shù)量級。

?

?

?

一點疑惑

問題:為什么這里填充了9個long值呢???

我提了一個issue:https://github.com/netty/netty/issues/9284

?

?

?

雖然也有人回答,但是感覺不是自己想要的,說服不了自己!!!

?

?

?

FastThreadLocal比ThreadLocal快,并不是空間換時間

?

?

?

?

?

現(xiàn)在清理已經(jīng)去掉,本文下面會介紹,所以FastThreadLocal比ThreadLocal快,并不是空間換時間,FastThreadLocal并沒有浪費空間!!!

FastThreadLocal不在使用ObjectCleaner處理泄漏,必要的時候建議重寫onRemoval方法

最新的netty版本中已經(jīng)不在使用ObjectCleaner處理泄漏:

https://github.com/netty/netty/commit/9b1a59df383559bc568b891d73c7cb040019aca6#diff-e0eb4e9a6ea15564e4ddd076c55978de

https://github.com/netty/netty/commit/5b1fe611a637c362a60b391079fff73b1a4ef912#diff-e0eb4e9a6ea15564e4ddd076c55978de

?

?

?

?

?

?

?

去掉原因:

https://github.com/netty/netty/issues/8017

?

?

?

我們看看FastThreadLocal的onRemoval

?

?

?

如果使用的是FastThreadLocalThread能保證調(diào)用的,重寫onRemoval做一些收尾狀態(tài)修改等等

?

?

?

FastThreadLocal為什么快?

FastThreadLocal操作元素的時候,使用常量下標(biāo)在數(shù)組中進(jìn)行定位元素來替代ThreadLocal通過哈希和哈希表,這個改動特別在頻繁使用的時候,效果更加顯著!計算該ThreadLocal需要存儲的位置是通過hash算法確定位置:

int i = key.threadLocalHashCode & (len-1);而FastThreadLocal就是一個常量下標(biāo)index,這個如果執(zhí)行次數(shù)很多也是有影響的。

并且FastThreadLocal利用緩存行的特性,FastThreadLocal是通過indexedVariables數(shù)組進(jìn)行存儲數(shù)據(jù)的,如果有多個FastThreadLocal的時候,也可以利用緩存行,比如一次indexedVariables數(shù)組第3個位置數(shù)據(jù),由于緩存的最小單位是緩存行,順便把后面的4、5、6等也緩存了,下次剛剛好改線程需要讀取另外的FastThreadLocal,這個FastThreadLocal的下標(biāo)就是5的時候,進(jìn)行讀取的時候就直接走緩存了,比走主存可能快2個數(shù)量級而ThreadLocal通過hash是分散的。

轉(zhuǎn)載于:https://www.cnblogs.com/CQqf2019/p/11155560.html

總結(jié)

以上是生活随笔為你收集整理的FastThreadLocal吞吐量居然是ThreadLocal的3倍的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

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