収集のオヌバヌヘッド

オブゞェクトを栌玍するずきに䜙分なメモリを消費するコレクションがいく぀あるのかず思いたしお。 同じタむプの芁玠リストずセットのストレヌゞを含む䞀般的なコレクションのオヌバヌヘッド枬定を実行し、結果を共通のスケゞュヌルに枛らしたした。 64ビットHotspot JVMJava 1.6の画像を次に瀺したす。





ただし、32ビットのホットスポットの堎合



以䞋では、どのように枬定が行われたかを説明し、写真がそのように芋える理由を理解しおみたす。



材料ず方法



枬定は、通垞のJava配列だけでなく、java.utilおよびjava.util.concurrentからの暙準Javaコレクションに察しおも行われたした。 IdentityHashSetおよびConcurrentHashSetコレクションは、Collections.newSetFromMapメ゜ッドを䜿甚しお、察応するマップから䜜成されたす。 すべおのコレクションは、最初に芁玠の数を指定せずにデフォルトで初期化され必芁な配列を陀く、その埌、addメ゜ッドを䜿甚しおテストオブゞェクトで満たされたしたそしお、配列に察しお割り圓おが実行されたした。 1から10,000たでの異なる数の芁玠を䜿甚しお、各タむプの玄500のコレクションが䜜成されたした。芁玠ずしお、ランダムな文字で構成されるランダムな長さの10,000の異なる文字列が䜿甚されたした。 原則ずしお、芁玠自䜓はConcurrentHashSetにのみ圱響し、わずかでも圱響を䞎えるため、グラフはどのデヌタでも同じように芋えたす。



配列を埋めた埌、メモリダンプがプロセスから取埗され、Eclipse Memory Analyzerを䜿甚しお分析されたした。これにより、オブゞェクト自䜓は含たれず、オヌバヌヘッドのみが含たれる各コレクションの保持セットが非垞に正確に蚈算されたした。 たずえば、次のようになりたす。



それでは、Excelにコピヌし、簡単な数孊挔算ず、グラフィカル゚ディタヌで少し远加の図面を䜿甚しおプロットしたす。



結果の議論



各コレクションは、オヌバヌヘッドコストの境界が䜎く、芁玠が倚いほど、コレクションに近いこずが倚いこずがわかりたす。 ただし、䞀郚のコレクションでは、オヌバヌヘッド関数が数孊的な意味でこの境界に収束するずは蚀えたせん。 たずえば、ArrayListは8バむト64ビットの堎合になっおいたすが、新しいメモリが割り圓おられるたびに12バむトにゞャンプし続けたす。



興味深いこずに、32ビットず64ビットのグラフは非垞に䌌おいたす。ほずんどのコレクションでは、ConcurrentSkipListSetずLinkedListの2぀の䟋倖を陀いお、グラフが半分異なりたす。 各コレクションを個別に怜蚎し、そのようなスケゞュヌルである理由を確認しおください。



配列


最も単玔なオプションは、芁玠の数が事前にわかっおいる配列です。 その䞭に、各オブゞェクトぞの参照が栌玍されたす48バむト括匧内の倀は64ビットJVMの堎合、さらに、配列の長さはint、4バむトで、オブゞェクト蚘述子は816バむトです。 さらに、各オブゞェクトは8バむトでアラむンされるため、32ビットごずに偶数個の芁玠を持぀配列は4バむトを倱いたす。 結果オブゞェクトごずに48バむトず12〜24バむトの定数。



空の配列は1624バむトを占有したす。



配列リスト


ここでは、わずかに違いはありたすが、ほずんど同じです。配列内の芁玠の数が事前にわからないため、配列にはマヌゞンデフォルトでは10芁玠が割り圓おられ、必芁に応じお1.5倍以䞊拡匵されたす。

int newCapacity = (oldCapacity * 3)/2 + 1;
      
      





したがっお、グラフは612バむトにゞャンプしたす。 定数もわずかに倧きくなりたす。4064バむト。配列に加えお、配列ぞの参照、リストの実際のサむズ、および倉曎の数ConcurrentModificationExceptionをスロヌするを栌玍するArrayListオブゞェクトもありたす。 それでも、これは、どれだけのデヌタがあるか事前にわからない堎合、同じタむプのデヌタを保存する最も経枈的な方法です。



芁玠を持たないデフォルトの構築されたArrayListは80144バむトを取りたす。



LinkedList


リンクリストの堎合、画像は配列のようなものです。双曲線の挞近線になりがちです。 java.util.LinkedList.Entryタむプの1぀のサヌビスオブゞェクトがリストアむテムごずに䜜成されたす。 これらの各オブゞェクトには3぀のリンクリスト項目自䜓、前および次の゚ントリぞが含たれ、32ビットでのアラむメントにより4バむトが倱われるため、最終的に各゚ントリには2440バむトが必芁です。 定数には、LinkedListオブゞェクトのハンドル、先頭の゚ントリずそのリンク、リストのサむズ、および倉曎の数が4880バむト含たれたす。 もちろん、ここではメモリが割り圓おられないため、空のリストは同じ量を占有したす。



ツリヌセット


䞀般に、䜿甚されるすべおのセットはマップに基づいおいたす。 堎合によっおは、個別の実装は倚少コンパクトになる可胜性がありたすが、䞀般的なコヌドはもちろん重芁です。



グラフは、LinkedListず配列のようにも芋えたす。 各芁玠に察しお、キヌ、倀、芪、巊および右の子の5぀のリンクを含むjava.util.TreeMap.Entryツリヌブランチが䜜成されたす。 それらに加えお、枝の色、赀たたは黒を瀺すブヌル倉数が保存されたす 赀黒朚を参照。 単䞀のブヌル倉数は4バむトを䜿甚するため、レコヌド党䜓は3264バむトを䜿甚したす。



TreeMapの定数デヌタは、コンパレヌタぞの参照、ツリヌのルヌトぞの参照LinkedListの倩䜓゚ントリずは異なり、ルヌトは意図された目的に䜿甚されたす-セットの実際の芁玠を参照、entrySetぞのリンク、navigableKeySet、descendingMapこれらのオブゞェクトはオンデマンドで䜜成されたす、倉曎のサむズず数。 TreeMapオブゞェクト蚘述子を䜿甚するず、4880バむトが取埗されたす。 TreeSet自䜓は、蚘述子ずTreeMapぞのリンクのみを远加したす。 合蚈で64104バむトが出力されたす。 空のセットの重量は同じです。 ずころで、メモリ消費はツリヌのバランスの皋床に䟝存したせん。



ハッシュセット


HashSetはHashMapに基づいおいたすが、これはTreeMapより少し耇雑です。 各芁玠に察しお、キヌぞのリンク、Entryに続く倀耇数の゚ントリがハッシュテヌブルの同じセルに入る堎合、およびハッシュ倀自䜓を含むjava.util.HashMap.Entry゚ントリが䜜成されたす。 党䜓で、゚ントリの重量は2448バむトです。



゚ントリに加えお、゚ントリぞのリンクを含むハッシュテヌブルもありたす。このテヌブルには、最初は16個の芁玠が含たれ、芁玠数がサむズの75を超えるず倍になりたす75はデフォルトのloadFactor倀で、コンストラクタで指定できたす。 ぀たり、デフォルトで構築する堎合、芁玠の数が12、24、48、96などを超えるずテヌブルが拡倧されたす2 n * 3、グラフの最埌のバヌストは6144芁玠です。 増加盎埌のテヌブルは、芁玠数の2 / 0.75 = 2.67倍です。぀たり、総消費量は芁玠あたり玄34.6769.33バむトです定数はカりントしたせん。 増加の盎前では、テヌブルは芁玠数の1 / 0.75 = 1.33倍であり、合蚈消費量は芁玠あたり29.3358.67バむトです。 メモリ䜿甚量は、ハッシュ衝突が発生する頻床ずは完党に独立しおいるこずに泚意しおください。



垌望する人は定数コンポヌネントを自分で蚈算できたす。デフォルトで初期化された空のHashSetの重さは136240バむトだず蚀いたす。



LinkedHashSet


HashSetずほが同じです。 java.util.LinkedHashMap.Entryを䜿甚しおjava.util.HashMap.Entryを継承し、前ず次の芁玠に2぀のリンクを远加するため、グラフはHashSetより816バむト高く、テヌブル拡匵37.3374.67の前に達したす以降-蚘録42.6785.33。 LinkedListのように、セットの芁玠を参照しない先頭の゚ントリが栌玍されるため、定数も増加したした。 新しく䜜成されたLinkedHashSetの重量は176320バむトです。



IdentityHashSetnewSetFromMap経由


IdentityHashMapは非垞に興味深いものです。 キヌを等しいではなく==ず比范し、System.identityHashCodeを䜿甚するこずにより、暙準のマップコントラクトに違反したす。 たた、Entryのようなオブゞェクトを䜜成せず、すべおのキヌず倀を1぀の配列偶数芁玠のキヌ、奇数芁玠の倀に単玔に栌玍するため、興味深いものです。 衝突が発生した堎合、リストは䜜成されたせんが、オブゞェクトは配列に沿った最初の空きセルに曞き蟌たれたす。 これにより、すべおのセットで蚘録的な䜎いオヌバヌヘッドコストが達成されたす。



IdentityHashMapは、2/3を超えるたびに配列のサむズを2倍にしたすHashMapずは異なり、この係数は構成できたせん。 デフォルトでは、32個の芁玠を持぀配列が䜜成されたす぀たり、配列のサむズは64です。 したがっお、21、42、85、170などを超えるず拡匵が発生したす[2 n / 3]、グラフの最埌のサヌゞは5461です。 拡匵前の配列には、IdentityHashMapのキヌの3倍、拡匵埌の6倍の芁玠が含たれおいたす。 したがっお、オヌバヌヘッドは芁玠ごずに1224〜2448バむトです。 デフォルトでは、空のセットはかなり倚く-344656バむトかかりたすが、すでに9぀の芁玠があるため、他のすべおのセットよりも経枈的になりたす。



ConcurrentHashSetnewSetFromMap経由


ConcurrentHashMapは、チャヌトが芁玠自䜓たたはハッシュ関数に䟝存する最初のコレクションです。 倧たかに蚀うず、これは固定数のセグメントデフォルトでは16のセットであり、各セグメントはHashMapの同期アナログです。 倉曎されたハッシュコヌドからのビットの䞀郚は、セグメントの遞択に䜿甚され、異なるセグメントぞのアクセスは䞊行しお発生する可胜性がありたす。 java.util.concurrent.ConcurrentHashMap.HashEntryはjava.util.HashMap.Entryずほずんど同じであるため、制限では、オヌバヌヘッドはHashMap自䜓のオヌバヌヘッドず同じです。 グラフが同時に䞊昇しないため、セグメントのサむズの増加は独立しお発生したす。たず、より倚くの芁玠があるセグメントが増加したす。



このコレクションは、16個のセグメントがすぐに開始され、それぞれが16個のレコヌドずいく぀かの補助フィヌルドを持぀テヌブルを持っおいるため、初期サむズ-13042328バむトの面でトップになりたした。 ただし、10,000個の芁玠の堎合、ConcurrentHashSetはHashSetよりも0.3だけ倧きくなりたす。



ConcurrentSkipListSet


ConcurrentSkipListMapを介しお実装され、私の意芋では、蚘述されたコレクションの䞭で最も耇雑です。 アルゎリズムのアむデアはHabréで説明されおいるため、ここでは詳しく説明したせん。 ここで埗られるメモリサむズはデヌタに䟝存したせんが、収集は擬䌌乱数ゞェネレヌタによっお開始されるため、非決定的です。 次の擬䌌乱数に基づいお、むンデックス゚ントリを远加するかどうかず、レベル数を決定したす。 1぀のjava.util.concurrent.ConcurrentSkipListMap.Nodeオブゞェクトは、キヌ、倀、および次のノヌドぞの参照を含む各芁玠に察しお必ず䜜成され、単玔に接続されたリストを圢成したす。 これにより、芁玠ごずに2440バむトが埗られたす。 さらに、2぀の芁玠ごずに玄1぀のむンデックス゚ントリjava.util.concurrent.ConcurrentSkipListMap.Indexが䜜成されたす最初のレベルでは、4番目ごずにレコヌドがあり、2番目には8番目ごずに、など。 各むンデックス゚ントリの重さはNodeず同じでありそこには3぀のリンクもありたす、合蚈で各芁玠には玄3660バむトが必芁です。 各レベルHeadIndexにはヘッドレコヌドがありたすが、それらはほずんどないほが芁玠数の察数ので、無芖できたす。



空のConcurrentSkipListSetには、1぀のHeadIndexず1぀の空のノヌドが䜜成されたす。 デフォルトで構築埌、コレクションは112200バむトかかりたす。



なんでこんなこず



結果はほずんど予期せず、私の盎感的なアむデアず矛盟しおいたした。 そのため、競合するコレクションは通垞のコレクションよりもはるかに倚く、LinkedHashSetはTreeSetずHashSetの間のどこかに配眮する必芁があるず考えたした。 たた、メモリ消費がオブゞェクト自䜓に䟝存しないこずは事実䞊ないこずも驚くべきこずでした。ツリヌのバランスの床合いやハッシュテヌブル内の衝突の数は䜕にも圱響せず、競合しないコレクションの堎合、オヌバヌヘッドのサむズを最倧1バむトの粟床で事前に決定できたす。 さたざたなコレクションの内郚構造を掘り䞋げるこずは興味深いこずでした。 この研究に具䜓的な実際的な利点はありたすかわかりたせん、誰もが自分で決めおください。



All Articles