Netty中的 FastThreadLocal,到底快不快?有多快?

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

關於FastThreadLocal,準備從這幾個方面進行講解:

  • FastThreadLocal 的使用
  • FastThreadLocal 並不是什麼情況都快,你要用對纔會快
  • FastThreadLocal 利用字節填充來解決僞共享問題
  • FastThreadLocal 比ThreadLocal快,並不是空間換時間
  • FastThreadLocal 不在使用ObjectCleaner處理泄漏,必要的時候建議重寫onRemoval方法
  • FastThreadLocal 爲什麼快?

FastThreadLocal的使用

FastThreadLocal用法上兼容ThreadLocal,使用示例代碼:

public class FastThreadLocalTest {
private static FastThreadLocal <Integer> fastThreadLocal = new FastThreadLocal<>();
public static void main(String[] args) {
 //if (thread instanceof FastThreadLocalThread) 使用FastThreadLocalThread更優,普通線程也可以 new FastThreadLocalThread(() -> {
for(int i = 0 ; i < 100; i++) {
fastThreadLocal. set(i);
System.out.println(Thread.currentThread().getName() + "===="+ fastThreadLocal.get());try {
Thread.sleep(200);
 } catch(InterruptedException e) {
e.printStackTrace();
}
}
}, "fastThreadLocal1").start();
new FastThreadLocalThread(() -> {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + "===="+ fastThreadLocal.get());try {
Thread.sleep(200);
} catch(InterruptedException e) {
e.printStackTrace();
}
}
}, "fastThreadLocal2").start();
}
}

代碼截圖:

Netty中的 FastThreadLocal,到底快不快?有多快?

 

代碼運行結果:

Netty中的 FastThreadLocal,到底快不快?有多快?

 

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

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

Netty中的 FastThreadLocal,到底快不快?有多快?

 

備註:通過上面的例子,我們發現FastThreadLocal和ThreadLocal在用法上面基本差不多,沒有什麼特別區別,個人認爲,這就是FastThreadLocal成功的地方,它就是要讓用戶用起來和ThreadLocal沒啥區別,要兼容!

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

由於構造FastThreadLocalThread的時候,通過FastThreadLocalRunnable對Runnable對象進行了包裝:


FastThreadLocalRunnable.wrap(target)``從而構造了FastThreadLocalRunnable對象。

Netty中的 FastThreadLocal,到底快不快?有多快?

 

FastThreadLocalRunnable在執行完之後都會調用FastThreadLocal.removeAll();

Netty中的 FastThreadLocal,到底快不快?有多快?

 

備註: FastThreadLocal不在使用ObjectCleaner處理泄漏,必要的時候建議重寫onRemoval方法。關於這塊將在本文後面進行介紹,這樣是很多網上資料比較老的原因,這塊已經去掉了。

如果是普通線程,還是應該最佳實踐:

finally {

fastThreadLocal.removeAll();

}

注意: 如果使用FastThreadLocal就不要使用普通線程,而應該構建FastThreadLocalThread,關於爲什麼這樣,關於這塊將在本文後面進行介紹:FastThreadLocal並不是什麼情況都快,你要用對纔會快。

FastThreadLocal並不是什麼情況都快,要用對纔會快

首先看看netty關於這塊的測試用例:代碼路徑:
https://github.com/netty/netty/blob/4.1/microbench/src/main/java/io/netty/microbench/concurrent/FastThreadLocalFastPathBenchmark.java

Netty中的 FastThreadLocal,到底快不快?有多快?

 

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

FastThreadLocal並不是什麼情況都快,你要用對纔會快!!!

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

Netty中的 FastThreadLocal,到底快不快?有多快?

 

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

  • FastThreadLocalFastPathBenchmark
  • FastThreadLocalSlowPathBenchmark

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


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

Netty中的 FastThreadLocal,到底快不快?有多快?

 


FastThreadLocalSlowPathBenchmark測試結果:比ThreadLocal的吞吐量還低。

Netty中的 FastThreadLocal,到底快不快?有多快?

 

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

Netty中的 FastThreadLocal,到底快不快?有多快?

 

註釋裏面給出了三點:

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

我們看看NioEventLoopGroup細節:

Netty中的 FastThreadLocal,到底快不快?有多快?

 

Netty中的 FastThreadLocal,到底快不快?有多快?

 

Netty中的 FastThreadLocal,到底快不快?有多快?

 

看到這裏,和剛剛我們看到的註釋內容一致的,是使用FastThreadLocalThread的。

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

池化內存分配:

Netty中的 FastThreadLocal,到底快不快?有多快?

 

會使用到Recycler

Netty中的 FastThreadLocal,到底快不快?有多快?

 

而Recycler也使用了FastThreadLocal

Netty中的 FastThreadLocal,到底快不快?有多快?

 

我們再看看看測試類:

Netty中的 FastThreadLocal,到底快不快?有多快?

 

備註:我們會發現
FastThreadLocalFastPathBenchmark裏面的線程是FastThreadLocal。

Netty中的 FastThreadLocal,到底快不快?有多快?

 

備註:我們會發現
FastThreadLocalSlowPathBenchmark裏面的線程不是FastThreadLocal

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

FastThreadLocal利用字節填充來解決僞共享問題

關於CPU 緩存 內容來源於美團:
https://tech.meituan.com/2016/11/18/disruptor.html

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

Netty中的 FastThreadLocal,到底快不快?有多快?

 

當CPU執行運算的時候,它先去L1查找所需的數據、再去L2、然後是L3,如果最後這些緩存中都沒有,所需的數據就要去主內存拿。走得越遠,運算耗費的時間就越長。所以如果你在做一些很頻繁的事,你要儘量確保數據在L1緩存中。

另外,線程之間共享一份數據的時候,需要一個線程把數據寫回主存,而另一個線程訪問主存中相應的數據。

下面是從CPU訪問不同層級數據的時間概念:

Netty中的 FastThreadLocal,到底快不快?有多快?

 

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

緩存行

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

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

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

僞共享

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

如果讓多線程頻繁操作的並且沒有關係的變量在不同的緩存行中,那麼就不會因爲緩存行的問題導致沒有關係的變量的修改去影響另外沒有修改的變量去讀主存了(那麼從L1中取是從主存取快2個數量級的)那麼性能就會好很多很多。

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

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

netty demo

Netty中的 FastThreadLocal,到底快不快?有多快?

 

Netty中的 FastThreadLocal,到底快不快?有多快?

 

利用字節填充來解決僞共享,從而速度快了3倍左右。

FastThreadLocal使用字節填充解決僞共享

之前介紹ThreadLocal的時候,說過ThreadLocal是用在多線程場景下,那麼FastThreadLocal也是用在多線程場景,所以FastThreadLocal需要解決僞共享問題,使用字節填充解決僞共享。

Netty中的 FastThreadLocal,到底快不快?有多快?

 

Netty中的 FastThreadLocal,到底快不快?有多快?

 

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

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

Netty中的 FastThreadLocal,到底快不快?有多快?

 

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

Netty中的 FastThreadLocal,到底快不快?有多快?

 

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

nettydemo

Netty中的 FastThreadLocal,到底快不快?有多快?

 

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

FastThreadLocal被FastThreadLocalThread進行讀寫的時候也可能利用到緩存行

Netty中的 FastThreadLocal,到底快不快?有多快?

 

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

Netty中的 FastThreadLocal,到底快不快?有多快?

 

一點疑惑

問題:爲什麼這裏填充了9個long值呢???

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

Netty中的 FastThreadLocal,到底快不快?有多快?

 

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

Netty中的 FastThreadLocal,到底快不快?有多快?

 

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

Netty中的 FastThreadLocal,到底快不快?有多快?

 

Netty中的 FastThreadLocal,到底快不快?有多快?

 

現在清理已經去掉,本文下面會介紹,所以FastThreadLocal比ThreadLocal快,並不是空間換時間,FastThreadLocal並沒有浪費空間!!!

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

最新的netty版本中已經不在使用ObjectCleaner處理泄漏:

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

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

Netty中的 FastThreadLocal,到底快不快?有多快?

 

Netty中的 FastThreadLocal,到底快不快?有多快?

 

Netty中的 FastThreadLocal,到底快不快?有多快?

 

去掉原因:

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

Netty中的 FastThreadLocal,到底快不快?有多快?

 

我們看看FastThreadLocal的onRemoval

Netty中的 FastThreadLocal,到底快不快?有多快?

 

如果使用的是FastThreadLocalThread能保證調用的,重寫onRemoval做一些收尾狀態修改等等

Netty中的 FastThreadLocal,到底快不快?有多快?

 

Netty中的 FastThreadLocal,到底快不快?有多快?

 

FastThreadLocal爲什麼快?

FastThreadLocal操作元素的時候,使用常量下標在數組中進行定位元素來替代ThreadLocal通過哈希和哈希表,這個改動特別在頻繁使用的時候,效果更加顯著!計算該ThreadLocal需要存儲的位置是通過hash算法確定位置:int i = key.threadLocalHashCode & (len-1);而FastThreadLocal就是一個常量下標index,這個如果執行次數很多也是有影響的。

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章