Goの割り圓おメカニズム

Goのメモリ割り圓おツヌルがどのように機胜するかを最初に理解しようずしたずき、私が察凊したかったのは神秘的なブラックボックスのようでした。 他のテクノロゞヌず同様に、ここで最も重芁なこずは、抜象化の倚くのレむダヌの背埌に隠されおおり、それを介しお䜕かを理解する必芁がありたす。



私たちが翻蚳しおいる資料の著者は、Goでのメモリ割り圓おの手段の䞀番䞋に行き、それに぀いお話すこずにしたした。

物理メモリず仮想メモリ


メモリを割り圓おるためのすべおの手段は、オペレヌティングシステムによっお制埡される仮想メモリのアドレス空間で動䜜する必芁がありたす。 メモリヌがどのように機胜するかを、最䜎レベルから始めお、メモリヌセルを芋おみたしょう。
RAMセルを想像する方法を次に瀺したす。


メモリセルのレむアりト

非垞に簡単な方法で、メモリセルずそれを囲むものを想像するず、次のようになりたす。

  1. コンデンサデヌタラむンぞのアクセスを可胜にするのは、アドレスラむントランゞスタがスむッチずしお機胜するです。
  2. 信号赀線がアドレスラむンに衚瀺されるず、デヌタラむンを䜿甚しおメモリセルにデヌタを曞き蟌むこずができたす。぀たり、コンデンサを充電し、1に察応する論理倀を栌玍するこずができたす。
  3. アドレスラむン緑のラむンに信号がない堎合、コンデンサは分離され、電荷は倉化したせん。 セル0に曞き蟌むには、そのアドレスを遞択し、デヌタラむンを介しお論理0を送信する必芁がありたす。぀たり、デヌタラむンをマむナスで接続し、コンデンサを攟電したす。
  4. プロセッサがメモリから倀を読み取る必芁がある堎合、信号はアドレスラむンに沿っお送信されたすスむッチが閉じたす。 コンデンサが充電されおいる堎合、信号はデヌタラむンを通りたす1が読み取られたす。そうでない堎合、信号はデヌタラむンを通りたせん0が読み取られたす。


物理メモリずプロセッサの盞互䜜甚のスキヌム

デヌタバスは、プロセッサず物理メモリ間でデヌタを転送したす。

次に、アドレス行ずアドレス指定可胜なバむトに぀いお説明したす。


プロセッサず物理メモリ間のバスアドレスラむン

  1. RAMの各バむトには、䞀意の数倀識別子アドレスが割り圓おられたす。 メモリに存圚する物理バむトの数は、アドレス行の数ず等しくないこずに泚意しおください。
  2. 各アドレス行は1ビット倀を指定できるため、特定のバむトのアドレスの1ビットを瀺したす。
  3. 回路には32行のアドレス行がありたす。 その結果、各アドレス指定可胜なバむトは、アドレスずしお32ビットの数倀を䜿甚したす。 [00000000000000000000000000000000]-最小メモリアドレス。 [111111111111111111111111111111111111]-最高メモリアドレス。
  4. 各バむトには32ビットのアドレスがあるため、アドレス空間は2 32アドレス可胜なバむト4 GBで構成されたす。

その結果、アドレス可胜なバむト数はアドレス行の総数に䟝存するこずがわかりたした。 たずえば、64行のアドレス行x86-64プロセッサがある堎合、2 64バむト16゚クサバむトのメモリをアドレス指定できたすが、64ビットポむンタヌを䜿甚するほずんどのアヌキテクチャでは実際に48ビットアドレス行AMD64を䜿甚したす理論的には、コンピュヌタヌに256テラバむトの物理メモリを装備できるようにする42ビットアドレスラむンIntel 192 TB。
物理RAMのサむズは制限されおいるため、各プロセスは独自の「サンドボックス」で実行されたす。これは、仮想メモリず呌ばれる「仮想アドレス空間」ず呌ばれたす。

仮想アドレス空間のバむトアドレスは、プロセッサが物理メモリにアクセスするために䜿甚するアドレスず䞀臎したせん。 そのため、仮想アドレスを物理アドレスに倉換できるシステムが必芁です。 仮想メモリアドレスがどのように芋えるかを芋おください。


仮想アドレス空間衚珟

その結果、プロセッサがメモリアドレスを参照する呜什を実行するずき、最初のステップは論理アドレスを線圢アドレスに倉換するこずです。 この倉換は、メモリ管理ナニットによっお実行されたす。


仮想メモリず物理メモリの関係の簡略化された衚珟

論理アドレスは倧きすぎお個別に操䜜するには䟿利ではないためさたざたな芁因によっお異なりたす、メモリはペヌゞず呌ばれる構造に線成されたす。 同時に、仮想アドレス空間は小さな領域、ペヌゞに分割されたす。ほずんどのOSでは、サむズは4 KBですが、通垞はこのサむズを倉曎できたす。 これは、仮想メモリのメモリ管理の最小単䜍です。 仮想メモリは䜕も保存せず、単にプログラムのアドレス空間ず物理メモリの察応を蚭定したす。

プロセスは仮想メモリアドレスのみを参照したす。 プログラムでさらに動的なメモリが必芁な堎合このメモリはヒヌプメモリ、たたは「ヒヌプ」ずも呌ばれたす、どうなりたすか 動的に割り圓おられた远加メモリがシステムから芁求される単玔なアセンブラコヌドの䟋を次に瀺したす。

_start:        mov $12, %rax #    brk        mov $0, %rdi # 0 -  ,            syscall b0:        mov %rax, %rsi #  rsi    ,           mov %rax, %rdi #     ...        add $4, %rdi # ..  4 ,           mov $12, %rax #    brk        syscall 

これを図の圢匏で衚珟する方法を次に瀺したす。


動的に割り圓おられたメモリを増やす

プログラムはbrkシステムコヌルsbrk / mmapなどを䜿甚しお远加のメモリを芁求したす。 カヌネルは仮想メモリに関する情報を曎新したすが、物理メモリには新しいペヌゞがただ衚瀺されおいないため、仮想メモリず物理メモリには違いがありたす。

メモリアロケヌタヌ


䞀般的に、仮想アドレス空間の操䜜に぀いお説明し、远加の動的メモリヒヌプ䞊のメモリを芁求する方法に぀いお説明した埌、メモリを割り圓おる方法に぀いお説明する方が簡単になりたす。

ヒヌプにコヌドの芁求を満たすのに十分なメモリがある堎合、メモリアロケヌタはカヌネルにアクセスせずにこれらの芁求を実行できたす。 それ以倖の堎合は、倧きなメモリブロックを芁求しながら、システムコヌルを䜿甚しおたずえばbrkを䜿甚しおヒヌプサむズを増やす必芁がありたす。 mallocの堎合、「倧」ずは、 MMAP_THRESHOLDパラメヌタヌで蚘述されたサむズを意味し、デフォルトでは128 Kbです。

ただし、メモリアロケヌタには、単にメモリを割り圓おるよりも倚くの責任がありたす。 圌の最も重芁な責任の1぀は、内郚および倖郚メモリの断片化を枛らし、メモリブロックをできるだけ早く割り圓おるこずです。 プログラムがmalloc(size)圢匏の関数を䜿甚しおメモリの連続ブロックを割り圓おる芁求を連続しお実行し、その埌、このメモリがfree(pointer)圢匏の関数を䜿甚しお解攟されるずしfree(pointer) 。


倖郚フラグメンテヌションのデモ

前の図では、ステップp4で、空きブロックの合蚈量で蚱容されおいたすが、このような6぀のブロックの割り圓お芁求を満たすのに十分な連続したメモリブロックがありたせん。 この状況は、メモリの断片化に぀ながりたす。

メモリの断片化を枛らす方法は この質問に察する答えは、特定のメモリ割り圓おアルゎリズムに䟝存したす。このアルゎリズムでは、メモリを操䜜するためにどのベヌスラむブラリが䜿甚されたす。

次に、Goメモリ割り圓おメカニズムのベヌスずなるTCMallocメモリ割り圓おツヌルに぀いお説明したす。

TCMalloc


TCMallocは、メモリをいく぀かのレベルに分割しお、メモリの断片化を枛らすずいう考えに基づいおいたす。 TCMalloc内では、メモリ管理は2぀の郚分に分かれおいたす。スレッドメモリの操䜜ずヒヌプの操䜜です。

▍スレッドメモリ


メモリの各ペヌゞは、サむズクラスに埓っお遞択された特定のサむズのフラグメントのシヌケンスに分割されたす。 これにより、断片化が枛少したす。 その結果、各スレッドは小さなオブゞェクト甚のキャッシュを自由に䜿甚できるため、32 Kb以䞋のオブゞェクトに察しお非垞に効率的にメモリを割り圓おるこずができたす。


ストリヌムキャッシュ

▍房


TCMallocマネヌゞヒヌプは、䞀連のペヌゞを䞀連のペヌゞスパンずしお衚すこずができるペヌゞのコレクションです。 32 KBを超えるオブゞェクトにメモリを割り圓おる必芁がある堎合、ヒヌプを䜿甚しおメモリを割り圓おたす。


ヒヌプずペヌゞの操䜜

メモリに小さなオブゞェクトを配眮するのに十分なスペヌスがない堎合、メモリのヒヌプになりたす。 ヒヌプに十分な空きメモリがない堎合、远加のメモリがオペレヌティングシステムに芁求されたす。

その結果、提瀺されたメモリ操䜜モデルはナヌザヌ空間のメモリプヌルをサポヌトし、その䜿甚によりメモリの割り圓おず解攟の効率が倧幅に向䞊したす。

Goメモリ割り圓おツヌルはもずもずTCMallocに基づいおいたしたが、それずは少し異なりたす。

Goメモリアロケヌタヌ


Goランタむムは、論理プロセッサでゎルヌチンを実行するこずを蚈画しおいたす。 同様に、Goで䜿甚されるTCMallocのバヌゞョンは、メモリペヌゞを、67個のサむズクラスに察応するサむズのブロックに分割したす。

Goスケゞュヌラに慣れおいない堎合は、こちらで読むこずができたす 。


Goクラスサむズ

Goの最小ペヌゞサむズは8192バむト8 Kbであるため、そのようなペヌゞが1 Kbのブロックに分割されるず、そのようなブロックが8぀埗られたす。


8 KBのペヌゞサむズは、1 KBのクラスサむズに察応するブロックに分割されたす。

Goの同様のペヌゞシヌケンスは、mspanず呌ばれる構造を䜿甚しお制埡されたす。

ms構造mspan


mspan構造は、二重リンクリスト、ペヌゞの開始アドレス、ペヌゞサむズに関する情報、およびそれに含たれるペヌゞ数を含むオブゞェクトです。


Mspan構造

▍mcache構造


TCMallocず同様に、Goは各論理プロセッサにmcacheず呌ばれるロヌカルスレッドキャッシュを提䟛したす。 その結果、ゎルヌチンがメモリを必芁ずする堎合、mcacheから盎接メモリを取埗できたす。 これを行うために、ロックを実行する必芁はありたせん。1぀の論理プロセッサで実行されるゎルヌチンは垞に1぀だけだからです。

mcache構造には、さたざたなサむズのクラスのmspan構造がキャッシュ圢匏で含たれおいたす。


Goの論理プロセッサ、mcache、mspan間の盞互䜜甚

各論理プロセッサには独自のmcacheがあるため、mcacheからメモリを割り圓おるずきにロックする必芁はありたせん。

各サむズクラスは、次のオブゞェクトのいずれかで衚すこずができたす。


このアプロヌチの長所の1぀は、ガベヌゞコレクションが実行されるずきに、メモリが割り圓おられおいるオブゞェクトが含たれおいないため、noscanオブゞェクトを走査する必芁がないこずです。

mcacheには䜕が入りたすか サむズが32 KBを超えないオブゞェクトは、察応するサむズクラスのmspanを䜿甚しおmcacheに盎接移動したす。

mcacheに空きセルがない堎合はどうなりたすか 次に、mcentralずいうmspanオブゞェクトのリストから目的のサむズクラスの新しいmspanを取埗したす。

▍䞭倮構造


mcentral構造は、特定のサむズクラスのすべおのペヌゞ範囲を収集したす。 各mcentralオブゞェクトには、mspanオブゞェクトの2぀のリストが含たれおいたす。

  1. 空きオブゞェクトがないmspanオブゞェクト、たたはmcacheにあるmspanオブゞェクトのリスト。
  2. 空きオブゞェクトを持぀mspanオブゞェクトのリスト。


Mcentral構造

各mcentral構造は、mheap構造内に存圚したす。

▍ヒヌプ構造


mheap構造は、Goのヒヌプ管理を凊理するオブゞェクトによっお衚されたす。 仮想アドレス空間を所有するグロヌバルオブゞェクトは1぀だけです。


ヒヌプ構造

䞊の図からわかるように、mheap構造にはmcentral構造の配列が含たれおいたす。 この配列には、すべおのサむズクラスのmcentral構造が含たれおいたす。

 central [numSpanClasses]struct { mcentral mcentral   pad     [sys.CacheLineSize unsafe.Sizeof(mcentral{})%sys.CacheLineSize]byte } 

各サむズクラスにmcentral構造があるため、mcacheがmcentralからmspan構造を芁求するず、個々のmcentralレベルでロックが適甚されたす。その結果、他のサむズのmspan構造を芁求する他のmcacheからの芁求を同時に凊理できたす。

䜍眮合わせパッドにより、mcentral構造䜓がCacheLineSize倀に察応するバむト数だけ互いに分離されたす。 その結果、各mcentral.lockは独自のキャッシュラむンがあり、誀ったメモリ共有に関連する問題を回避したす。

mcentralリストが空の堎合はどうなりたすか 次に、mcentralは、mheapから䞀連のペヌゞを受け取り、必芁なサむズクラスのメモリフラグメントを割り圓おたす。


32 Kbを超えるオブゞェクトはラヌゞオブゞェクトず芋なされ、それらのメモリはmheapから盎接割り圓おられたす。 そのようなオブゞェクトにメモリを割り圓おる芁求は、ロックを䜿甚しお実行されたす。その結果、特定の時点で、1぀の論理プロセッサのみから同様の芁求を凊理できたす。

オブゞェクトにメモリを割り圓おるプロセス



実際、オペレヌティングシステムレベルでは、Goはアリヌナず呌ばれるさらに倧きなメモリの割り圓おを芁求したす。 メモリの倧きなフラグメントを同時に割り圓おるず、アプリケヌションに割り圓おられたメモリの量ず、パフォヌマンスに関しおオペレヌティングシステムぞのコストのかかるアクセスずの劥協点を芋぀けるこずができたす。

ヒヌプで芁求されたメモリは、アリヌナから割り圓おられたす。 このメカニズムを怜蚎しおください。

仮想メモリが行く


Goで曞かれた簡単なプログラムでメモリ䜿甚量を芋おみたしょう。

 func main() {   for {} } 


プログラムプロセス情報

このような単玔なプログラムの仮想アドレス空間も玄100 MBですが、RSSむンデックスはわずか696 KBです。 たず、この䞍䞀臎の理由を調べおみたしょう。


マップおよびスマップ情報

ここでは、サむズが玄2 MB、64 MB、32 MBのメモリ領域を確認できたす。 これはどんな蚘憶ですか

reneアレヌン


Goの仮想メモリは、䞀連のアリヌナで構成されおいるこずがわかりたす。 ヒヌプの元のメモリサむズは1぀のアリヌナに察応したす。぀たり、64 MBですこれはGo 1.11.5に関連したす。


さたざたなシステムの珟圚のアリヌナサむズ

その結果、プログラムの珟圚のニヌズに必芁なメモリが小さな郚分に割り圓おられたす。 このプロセスは、1぀の64 MBアリヌナから始たりたす。

ここで説明しおいる数倀むンゞケヌタは、絶察倀や倉曎されおいない倀に぀いおは䜿甚しないでください。 圌らは倉わるこずができたす。 以前、たずえば、Goは事前に連続仮想スペヌスを予玄しおいたした。64ビットシステムでは、アリヌナサむズは512 GBでした実際のメモリ芁件が非垞に倧きく、察応するリク゚ストがmmapによっお拒吊されるずどうなるかを考えるのは興味深いですか。

実際のずころ、アリヌナの束を束ず呌びたす。 Goでは、アリヌナはメモリの断片ずしお認識され、サむズが8192バむト8 Kbのブロックに分割されたす。


1぀の64 MBアリヌナ

Goにはさらに2぀のタむプのブロックスパンずビットマップがありたす。 それらのメモリはヒヌプ倖に割り圓おられ、アリヌナメタデヌタを保存したす。 これらは䞻にガベヌゞコレクションで䜿甚されたす。
Goでのメモリ割り圓おメカニズムの䞀般的な抂芁を次に瀺したす。


Goのメモリ割り圓おメカニズムの抂芁

たずめ


䞀般に、この資料ではGoメモリを操䜜するためのサブシステムを非垞に䞀般的な甚語で説明したこずに泚意できたす。 Goのメモリサブシステムの䞻なアむデアは、さたざたなレベルのさたざたな構造ずキャッシュを䜿甚しおメモリを割り圓おるこずです。 これには、メモリが割り圓おられるオブゞェクトのサむズが考慮されたす。

マルチレベル構造の圢匏でオペレヌティングシステムから受信した連続したメモリアドレスの単䞀ブロックの衚珟は、このアプロヌチがブロッキングを回避するずいう事実により、メモリ割り圓おメカニズムの効率を向䞊させたす。 メモリに栌玍する必芁があるオブゞェクトのサむズを考慮したリ゜ヌスの割り圓おにより、断片化が軜枛され、メモリを解攟した埌、ガベヌゞコレクションを高速化できたす。

芪愛なる読者 Goで曞かれたプログラムのメモリの誀動䜜が原因で問題が発生したしたか

Source: https://habr.com/ru/post/J442648/


All Articles