Netty 5学習ノート-メモリプール5-PoolThreadCache
前述のPoolArena解析を見ると、PoolArenaはメモリの割り当て時にsynchronizedを使用してスレッドの安全を保証し、一定の効率の問題をもたらすことがわかります.どのようにこれ以上最適化することができますか?答えは簡単で、ThreadLocalのようなソリューションを使ってロックを避けることができます!なぜThreadLocalが使用できるのか、nettyスレッドモデル(興味があれば後で一緒に学ぶことができる)を知っていれば、nettyのwokerスレッドプールがデータの受信、送信(コーデックはここでは先に言わない)を担当し、同じ接続が同じスレッドでこの2つのステップを完了することを保証するため、ThreadLocalを利用してメモリの割り当てと回収の最適化を容易に行うことができる.
次に、メモリプールのメモリ割り当てプロセスを見てみましょう.
1、ByteBufAllocatorはメモリを申請するつもりです.
2、PoolThreadCacheから使用可能なメモリを取得してみます.成功すれば、今回の割り当てを完了します.そうしないと、下に進み続け、後のメモリ割り当てがロックされていることに注意してください.
3、小さなブロック(この値を構成可能)のメモリ割付であれば、PoolArenaにキャッシュされたPoolSubpageからメモリを取得し、成功すれば今回の割付を完了する.
4、通常サイズのメモリ割り当てであれば、PoolChunkListから利用可能なPoolChunkを検索してメモリ割り当てを行い、利用可能なPoolChunkがなければ1つ作成してPoolChunkListに追加し、今回のメモリ割り当てを完了する.
5、大きなブロック(chunkより大きいサイズ)のメモリ割り当ての場合、メモリプールを使わずにメモリを直接割り当てる方法.
6、メモリの使用が完了したらリリースし、リリースするときはまず、割り当てたときと同じスレッドであるかどうかを判断し、もしそうであればPoolThreadCacheに入れてみます.このメモリは、次の同じスレッドでメモリを申請するときに使用されます.つまり、前のステップ2です.
7、同じスレッドでない場合、chunkに回収され、このときchunkのメモリ使用率が変化し、そのchunkが異なるPoolChunkListで移動したり、chunk全体が回収されたりする可能性がある(chunkはq 000にあり、割り当てられたすべてのメモリが解放される).また、ステップ3で説明したメモリと同じ小さなメモリを解放した場合は、PoolArenaに小さなメモリをプリセットしようとします.ここでは操作が成功し、ステップ3の操作で成功する可能性があります.
前のいくつかのステップから、netty自体のスレッドモデルを加えると、ThreadLocalが使用される確率が高いことがわかります(自分のネットワークioでも使用できます).
ThreadLocalが管理するオブジェクトはPoolThreadCacheであり、tinysmallormalのメモリ割り当ての違い(tinyとsmallはsubpage、normalはpage)により、PoolThreadCacheにはtinySubPageHeapCaches、smallSubPageHeapCaches、normalSubPageHeapCachesの3つの配列があり、データの使い方はPoolArenaのsubpagePoolsと同じであり、異なるindexは異なるサイズの対応するオブジェクトを配置し、ここで配列に対応するクラスはMemoryRegionCacheであり、そのいくつかの重要な属性を見てみましょう.
entries:割り当て可能なメモリの配列を格納し、entryはchunkのメモリIDを維持します.
head:entriesに現在最初に割り当てられるメモリの場所.tail:現在の配列にデータを置くことができる最初の使用可能な場所.
初期化時head=tail=0、cacheに徐々にデータが加わるにつれてtailは増加し続け、entries以上になる.lengthは0に戻り,リング配列を形成する.同時にentriesにデータを置くたびに、tailの対応する位置に使用可能なchunkがあることが判明すると、プールがいっぱいになったことを示し、cacheへの参加を放棄します.メモリの割り当てが継続するにつれてheadも増加し、その位置のentryが空の場合、cacheに使用可能なメモリがないことを示し、上記の手順3に進んでメモリの割り当てを行います.
maxUnusedCached:entries.length/2
maxEntriesInUse:cacheの最大同時使用数;
EntriesInUse:cacheで使用されているメモリの数.
cacheの多重化率が高くない場合、cahe内のデータを縮小しようとします.freeSweep AllocationThresholdを実行するたびに、次のように論理的に縮小します.
1.free=cacheで使用可能なメモリセグメントの数-最大同時使用数、およびメモリ割り当てピーク時にfree個の残りのメモリセグメントが存在する可能性がある.
2.free>maxUnusedCachedの場合は、cacheで利用可能な数が多く、使用頻度が低く(maxEntriesInUseが小さい)、無駄になる可能性があることを示します.このときheadからfree個のメモリセグメントが解放されます.
ThreadLocalを使用したことがある学生は、不適切な使用はメモリの漏洩を招きやすいことを知っていますが、nettyではThreadDeathWatcherを使用してメモリの漏洩を防止しています.ThreadDeathWatcherの論理は簡単で、1 sごとに指定されたThreadがaliveかどうかを判断し、スレッドが停止した場合、指定された論理を実行します.
これで、nettyメモリプールに直接関連するコードが分析され、実際にはnettyが2つの比較的古典的な割り当てポリシーを実現していることがわかります.buddy allocation(PoolChunkを参照)とjemalloc(PooledByteBufAllocator+PoolArena+PoolChunk+PoolThreadCache)ですので、更新された情報を知りたければ、上記の2つのキーワードで検索することができます.
次に、メモリプールのメモリ割り当てプロセスを見てみましょう.
1、ByteBufAllocatorはメモリを申請するつもりです.
2、PoolThreadCacheから使用可能なメモリを取得してみます.成功すれば、今回の割り当てを完了します.そうしないと、下に進み続け、後のメモリ割り当てがロックされていることに注意してください.
3、小さなブロック(この値を構成可能)のメモリ割付であれば、PoolArenaにキャッシュされたPoolSubpageからメモリを取得し、成功すれば今回の割付を完了する.
4、通常サイズのメモリ割り当てであれば、PoolChunkListから利用可能なPoolChunkを検索してメモリ割り当てを行い、利用可能なPoolChunkがなければ1つ作成してPoolChunkListに追加し、今回のメモリ割り当てを完了する.
5、大きなブロック(chunkより大きいサイズ)のメモリ割り当ての場合、メモリプールを使わずにメモリを直接割り当てる方法.
6、メモリの使用が完了したらリリースし、リリースするときはまず、割り当てたときと同じスレッドであるかどうかを判断し、もしそうであればPoolThreadCacheに入れてみます.このメモリは、次の同じスレッドでメモリを申請するときに使用されます.つまり、前のステップ2です.
7、同じスレッドでない場合、chunkに回収され、このときchunkのメモリ使用率が変化し、そのchunkが異なるPoolChunkListで移動したり、chunk全体が回収されたりする可能性がある(chunkはq 000にあり、割り当てられたすべてのメモリが解放される).また、ステップ3で説明したメモリと同じ小さなメモリを解放した場合は、PoolArenaに小さなメモリをプリセットしようとします.ここでは操作が成功し、ステップ3の操作で成功する可能性があります.
前のいくつかのステップから、netty自体のスレッドモデルを加えると、ThreadLocalが使用される確率が高いことがわかります(自分のネットワークioでも使用できます).
ThreadLocalが管理するオブジェクトはPoolThreadCacheであり、tinysmallormalのメモリ割り当ての違い(tinyとsmallはsubpage、normalはpage)により、PoolThreadCacheにはtinySubPageHeapCaches、smallSubPageHeapCaches、normalSubPageHeapCachesの3つの配列があり、データの使い方はPoolArenaのsubpagePoolsと同じであり、異なるindexは異なるサイズの対応するオブジェクトを配置し、ここで配列に対応するクラスはMemoryRegionCacheであり、そのいくつかの重要な属性を見てみましょう.
private final Entry<T>[] entries;
private final int maxUnusedCached;
private int head;
private int tail;
private int maxEntriesInUse;
private int entriesInUse;
private static final class Entry<T> {
PoolChunk<T> chunk;
long handle;
}
entries:割り当て可能なメモリの配列を格納し、entryはchunkのメモリIDを維持します.
head:entriesに現在最初に割り当てられるメモリの場所.tail:現在の配列にデータを置くことができる最初の使用可能な場所.
初期化時head=tail=0、cacheに徐々にデータが加わるにつれてtailは増加し続け、entries以上になる.lengthは0に戻り,リング配列を形成する.同時にentriesにデータを置くたびに、tailの対応する位置に使用可能なchunkがあることが判明すると、プールがいっぱいになったことを示し、cacheへの参加を放棄します.メモリの割り当てが継続するにつれてheadも増加し、その位置のentryが空の場合、cacheに使用可能なメモリがないことを示し、上記の手順3に進んでメモリの割り当てを行います.
maxUnusedCached:entries.length/2
maxEntriesInUse:cacheの最大同時使用数;
EntriesInUse:cacheで使用されているメモリの数.
cacheの多重化率が高くない場合、cahe内のデータを縮小しようとします.freeSweep AllocationThresholdを実行するたびに、次のように論理的に縮小します.
1.free=cacheで使用可能なメモリセグメントの数-最大同時使用数、およびメモリ割り当てピーク時にfree個の残りのメモリセグメントが存在する可能性がある.
2.free>maxUnusedCachedの場合は、cacheで利用可能な数が多く、使用頻度が低く(maxEntriesInUseが小さい)、無駄になる可能性があることを示します.このときheadからfree個のメモリセグメントが解放されます.
ThreadLocalを使用したことがある学生は、不適切な使用はメモリの漏洩を招きやすいことを知っていますが、nettyではThreadDeathWatcherを使用してメモリの漏洩を防止しています.ThreadDeathWatcherの論理は簡単で、1 sごとに指定されたThreadがaliveかどうかを判断し、スレッドが停止した場合、指定された論理を実行します.
ThreadDeathWatcher.watch(thread, freeTask);
// thread freeTask
private final Runnable freeTask = new Runnable() {
@Override
public void run() {
//
free0();
}
};
これで、nettyメモリプールに直接関連するコードが分析され、実際にはnettyが2つの比較的古典的な割り当てポリシーを実現していることがわかります.buddy allocation(PoolChunkを参照)とjemalloc(PooledByteBufAllocator+PoolArena+PoolChunk+PoolThreadCache)ですので、更新された情報を知りたければ、上記の2つのキーワードで検索することができます.