[netty4][netty-common]FastThreadLocal及其相關類系列以及CPU cacheline padding補齊優化的相關知識

FastThreadLocal

概述: ThreadLocal的一個特定變種改善,有更好的存取性能。
內部採用一個數組來代替ThreadLocal內部的hash表來存放變量。雖然這看起來是微不足道的,可是他確實比hash表性能好那麼一點,在頻繁存取時會更明顯。 若是用DefaultThreadFactory建立線程,那麼默認建立出來的就是FastThreadLocalThread,就會用FastThreadLocal。javascript

set數據靠InternalThreadLocalMap維護,InternalThreadLocalMap內部靠一個數組(就是上面說的)維護變量數據。html

擴展了什麼:
按ThreadLocal API的約定行爲,依賴InternalThreadLocalMap實現了這些行爲,諸如get、set、remove等。
remove支持onRemoval回調。java

InternalThreadLocalMap

自身實例獲取

get方法是對外暴露去自身實例的,有兩種方式取到InternalThreadLocalMap實例:git

  • 若是當前線程是FastThreadLocalThreadInternal,直接取其實例變量ThreadLocalMap,內部稱之爲fastGet。
  • 若是是JDK的Thread,那麼靠JDK的TheadLocal取到ThreadLocalMap,內部稱之爲slowGet。

數據存取

真正的存取變量是靠indexedVariablesetIndexedVariable方法完成。
閱讀代碼不難發現,是靠Object[] indexedVariables這個數組達成數據存儲的目的。github

存放數據的數組擴容

indexedVariables數組靠expandIndexedVariableTableAndSet動態擴容。初始長度是32。
擴容算法有點意思,是比當前index小的最大的2的n次方的值擴一倍,好比當前index是132,那麼就會擴成256長度的數組。ajax

Object[] oldArray = indexedVariables;
final int oldCapacity = oldArray.length;
int newCapacity = index;
newCapacity |= newCapacity >>>  1;
newCapacity |= newCapacity >>>  2;
newCapacity |= newCapacity >>>  4;
newCapacity |= newCapacity >>>  8;
newCapacity |= newCapacity >>> 16;
newCapacity ++;

是否徹底用數組存放數據?

不徹底是。由於InternalThreadLocalMap的父類UnpaddedInternalThreadLocalMap自帶了一些經常使用的字段:算法

  • futureListenerStackDepth
  • localChannelReaderStackDepth
  • handlerSharableCache
  • counterHashCode
  • random
  • typeParameterMatcherGetCache
  • typeParameterMatcherFindCache
  • stringBuilder
  • charsetEncoderCache
  • charsetDecoderCache
  • arrayList
    這個11個是靠實例字段直接存儲。
    另外此類,還用了padding補齊的手段優化了CPU cacheline僞共享的問題。我猜想性能提高主要來源於此。
// Cache line padding (must be public)
// With CompressedOops enabled, an instance of this class should occupy at least 128 bytes.
public long rp1, rp2, rp3, rp4, rp5, rp6, rp7, rp8, rp9;

關於padding補齊

該類爲了解決cache line僞共享的問題,採用了padding補齊。
該類(4.1.32.Final版本)補齊後經過sizeOfObject(也能夠用jol)算出來大小是136。 jol針對idea是有插件的,不像JDK帶的jol要運行起來才能計算對象大小。idea那個插件是針對語法樹分析後算的,由於就算你的類有編譯錯誤,他也能算出來。使用時注意選擇相應的壓縮模式,右上角。
關於jol有官方的sample是很不錯的,還有個博客寫的還行,包括字段重排等都有。json

爲何是136?按說128就行啊? 這個事情我以前也思索了很久沒有答案。直至翦哥今天跟我提到了一個別人前幾天提的issue,才翻到原來有人和咱們有同樣的困惑,並且答案居然是在netty版本迭代過程當中InternalThreadLocalMap的父類加了個字段:ArrayList arrayList;,致使變成了136,以前4.0.33Final版本就是128,我確實使用了這個版本進行了驗證,確實是。.... 居然是這樣,難以想象。

jiangxinlingdu commented 2 days ago
I have checked the code in old version and found that the size of InternalThreadLocalMap is 128Bytes in version 4.0.33. And now in latest code in github the size of InternalThreadLocalMap is 136. And the reason is that some has added two parameters: cleanerFlags (in class InternalThreadLocalMap) and arrayList (in parent class UnpaddedInternalThreadLocalMap).
In my view, the contributors has pushed the two parameters ignoring the Cache line padding. So it is a problem!markdown

My doubt is solved by you, thank you!!!

FastThreadLocalThread與FastThreadLocalRunnable

FastThreadLocalThread概述:綁定了InternalThreadLocalMap的線程類。繼承於JDK的Thread
FastThreadLocalThread擴展了什麼:

  • 主要對外暴露了獲取與設置InternalThreadLocalMap字段的接口。
  • 增長cleanupFastThreadLocals字段並在有Runnable參數的構造函數裏,會將cleanupFastThreadLocals字段設置成true。

由於若是經過FastThreadnLocalThread的有Runnable參數的構造函數構造的FastThreadLocalThread實例時,會將Runnable實例wrap成FastThreadLocalRunnable實例。 FastThreadLocalRunnable又會在其run方法中以finally的方式進行清理當前線程上全部的FastThreadLocal實例中的數據。

@Override
public void run() {
    try {
        runnable.run();
    } finally {
        FastThreadLocal.removeAll();
    }
}

因此cleanupFastThreadLocals字段意思是此線程在執行完成時清理當前線程上全部的FastThreadLocal實例中的數據。

FastThreadLocalRunnable擴展了什麼:

  • 如上面所說,會在run方法中用finally清理當前線程上全部的FastThreadLocal實例中的數據。

DefaultThreadFactory

擴展了什麼:

  • 實現了線程名前綴+自增線程號的模式
  • 實現了建立線程時默認使用FastThreadLocalThread實現
數組

相關文章
相關標籤/搜索