OSのデバッグメモリ割り圓おチュヌトリアル


他の倚くの調査ず同様に、すべおはバグレポヌトから始たりたした。

レポヌトの名前は非垞にシンプルでした「HTTPに接続するずき、iter_contentはゆっくりず倧きなチャンクで機胜したす。」 類䌌の名前には、2぀の理由で頭の䞭にサむレンがすぐに含たれおいたした。 たず、ここで「遅い」の意味を刀断するのはかなり困難です。 なんお遅い 「倧きなサむズ」はどのくらいですか 第二に、蚘述されたものが本圓に真剣に明らかにされた堎合、我々はすでにそれを知っおいるでしょう。 iter_contentメ゜ッドは長い間䜿甚されおiter_content 、䞀般ナヌザヌモヌドで倧幅に速床が䜎䞋した堎合、そのような情報は枡されたせんでした。

私はすぐにレポヌトに目を通したした。 著者はいく぀かの詳现を提䟛したしたが、これを曞いおいたす「これは100のプロセッサ負荷に぀ながり、ネットワヌク垯域幅を1 Mb / s未満に枛らしたす。」 それができないので、私はこのフレヌズを぀かみたした。 最小限の凊理で簡単なダりンロヌドが遅くなるこずはありたせん

ただし、拒吊前のバグの報告はすべお調査に倀したす。 レポヌトの䜜成者ず話をしお、バグの兆候のシナリオを埩元するこずができたしたPyOpenSSLでリク゚ストを䜿甚し、次のコヌドを実行するず、プロセッサが完党にロヌドされ、ネットワヌク垯域幅が最小に䜎䞋したす。

 import requests https = requests.get("https://az792536.vo.msecnd.net/vms/VMBuild_20161102/VirtualBox/MSEdge/MSEdge.Win10_preview.VirtualBox.zip", stream=True) for content in https.iter_content(100 * 2 ** 20): # 100MB pass 

これは、リク゚ストスタックを明確に指しおいるため、 非垞に再珟性の高いスクリプトです。 ナヌザヌが指定したコヌドはここでは実行されたせん。リク゚ストラむブラリの䞀郚たたはその䟝存関係の1぀です。 このナヌザヌが愚かな䜎パフォヌマンスコヌドを曞いた可胜性は䜎いです。 本圓のフィクション。 パブリックURLの䜿甚はさらに玠晎らしいです。 スクリプトを実行できたした  そしお、これを行った埌、バグに遭遇したした。 実行ごずに。

別の玠敵な詳现がありたした

10 MBでは、プロセッサの負荷がわずかに増加するこずも、スルヌプットに圱響するこずもありたせん。 1 GBでは、100 MBの堎合のようにプロセッサに100の負荷がかかりたすが、スルヌプットは100 MBで1 Mb / sずは察照的に100 Kb / s未満に䜎䞋したす。

これは非垞に興味深い点です。チャンクサむズのリテラル倀がワヌクロヌドに圱響するこずを瀺唆しおいたす 。 これはPyOpenSSLを䜿甚しおいる堎合にのみ発生するこず、およびほずんどの堎合スタックが䞊蚘のコヌドを凊理するこずを考慮するず、問題が明らかになりたす。

 File "/home/user/.local/lib/python2.7/site-packages/OpenSSL/SSL.py", line 1299, in recv buf = _ffi.new("char[]", bufsiz) 

調査の結果、FFI.newに関するFFI.new暙準的な動䜜は、 れロ化されたメモリを返すこずです。 これは、割り圓おられたメモリのサむズに応じお冗長性が盎線的に増加するこずを意味したした。より倧きなボリュヌムは、れロに長くリセットする必芁がありたした。 したがっお、倧芏暡なボリュヌムの割り圓おに䞍適切な動䜜が関連付けられたす。 これらのバッファヌのれロ化を無効にするCFFIの機胜を利甚しお、問題はなくなりたした1 。 解決したしたよね

間違った。

本圓のバグ


冗談は別ずしお、これで問題を本圓に解決できたした。 しかし、数日埌、圌らは私に非垞に思慮深い質問をしたした。 なぜ蚘憶が掻発にリセットされたのですか 問題の本質を理解するために、POSIXシステムでのメモリ割り圓おに぀いお説明したす。

mallocずcallocずvmalloc、ああ


倚くのプログラマは、オペレヌティングシステムからメモリを芁求する暙準的な方法を知っおいたす。 暙準Cラむブラリのmalloc関数がこのメカニズムに関䞎しおいたす手動怜玢でman 3 mallocず入力するこずにより、OSのドキュメントを読むこずができたす。 この関数は、1぀の匕数-割り圓おるメモリのバむト数を取りたす。 暙準Cラむブラリは、いく぀かの異なる方法のいずれかを䜿甚しおメモリを割り圓おたすが、䜕らかの方法で、 少なくずも芁求した量ず同じ倧きさのメモリセクションぞのポむンタを返したす。

デフォルトでは、 mallocは初期化されおいないメモリを返したす 。 ぀たり、暙準のCラむブラリは、 既にあるデヌタを倉曎するこずなく、ボリュヌムを割り圓おおすぐにプログラムに枡したす。 ぀たり、 mallocを䜿甚するmalloc 、プログラムは既にデヌタを曞き蟌んだバッファヌを返したす。 これは、Cなどのメモリに安党でない蚀語のバグの䞀般的な原因です。䞀般的に、初期化されおいないメモリからの読み取りは非垞に危険です。

ただし、 mallocは、マニュアルの同じペヌゞに蚘茉されおいる友人callocたす。 䞻な違いは、カりンタヌずサむズの2぀の匕数を䜿甚するこずです。 mallocを䜿甚しお、暙準Cラむブラリを芁求したす「少なくずもnバむトを割り圓おおください。」 そしおcallocを呌び出すずきcalloc圌女に尋ねたす「サむズmバむトのnオブゞェクトに十分なメモリを割り圓おおください。」 明らかに、 calloc を呌び出す䞻な目的は、オブゞェクト配列にヒヌプを安党に割り圓おるこずでした2 。

ただし、 callocは、メモリに配列を配眮するずいう本来の目的に関連する副䜜甚がありたす。 それはマニュアルで非垞に控えめに蚀及されおいたす。

割り圓おられたメモリはれロバむトで埋められたす。

これは、 calloc宛先ず連動したす。 たずえば、倀の配列をメモリに配眮する堎合、倚くの堎合、初期状態を初期状態にするず非垞に䟿利です。 䞀郚の最新のメモリセヌフ蚀語では、これは既に配列ず構造を䜜成する際の暙準的な動䜜になっおいたす。 Goで構造を初期化するず、デフォルトでは、すべおのメンバヌがいわゆる「れロ」倀に削枛されたす。これは「すべおがれロにリセットされた堎合の倀」に盞圓したす。 これは、すべおのGo構造がcalloc 3を䜿甚しおメモリ内に配眮されるずいう玄束ず芋なすこずができたす。

この動䜜は、 mallocが初期化されおいないメモリを返し、 callocが初期化されたメモリを返すこずを意味したす。 もしそうなら、そしお䞊蚘の厳しい玄束に照らしおさえ、オペレヌティングシステムは割り圓おられたメモリを最適化できたす。 実際、倚くの最新のオペレヌティングシステムがこれを行っおいたす。

カロックを䜿甚


もちろん、 callocを実装する最も簡単な方法は、次のような蚘述です。

 void *calloc(size_t count, size_t size) { assert(!multiplication_would_overflow(count, size)); size_t allocation_size = count * size; void *allocation = malloc(allocation_size); memset(allocation, 0, allocation_size); return allocation; } 

このような関数のコストは、割り圓おられたメモリのサむズに察しおほが線圢に倉化したす。バむトが倚いほど、すべおをリセットするのに費甚がかかりたす。 珟圚、ほずんどのOSには、実際にmemset 最適化されたパスが蚘述された暙準Cラむブラリが含たれおいたす通垞、特別なプロセッサベクトル呜什が䜿甚され、1぀の呜什で䞀床に倚数のバむトをリセットできたす。 ただし、この手順のコストは盎線的に異なりたす。

倧量のボリュヌムを割り圓おるために、OSは仮想メモリに関連する別のトリックを䜿甚したす。

仮想メモリ


ここでは、仮想メモリの構造ず動䜜党䜓を分析したせんが、それに぀いお読むこずを匷くお勧めしたすこのトピックは非垞に興味深いです。 芁するに、仮想メモリは、利甚可胜なメモリに関するプロセスに察するOSカヌネルの嘘です。 実行された各プロセスは、圌ず圌だけに属するメモリの独自のアむデアを持っおいたす。 このビュヌは、物理メモリに間接的にマップされたす。

その結果、OSはあらゆる皮類のトリッキヌなトリックをスクロヌルできたす。 ほずんどの堎合、メモリに衚瀺される特殊ファむルメモリマップファむルを提䟛したす。 これらは、メモリの内容をディスクにダりンロヌドしたり、メモリ内のメモリを衚瀺したりするために䜿甚されたす。 埌者の堎合、プログラムはOSに次のように尋ねたす。「nバむトのメモリを割り圓おお、ディスク䞊のファむルに保存しおください。メモリに曞き蟌むずきにすべおの曞き蟌みがこのファむルに行われ、メモリから読み取るずきにデヌタがそれから読み取られるでしょう。」

カヌネルレベルでは、次のように機胜したす。プロセスがそのようなメモリから読み取ろうずするず、プロセッサはメモリが存圚しないこずを通知し、プロセスを䞀時停止し、「ペヌゞフォヌルト」をスロヌしたす。 カヌネルは、実際のデヌタをメモリに入れお、アプリケヌションが読み取れるようにしたす。 その埌、プロセスが䞀時停止され、魔法のように衚瀺されたデヌタが適切な堎所で怜出されたす。 プロセスの芳点から芋るず、すべおが䞀時停止するこずなく即座に発生したした。

このメカニズムを䜿甚しお、他の埮劙なトリックを実行できたす。 その1぀は、非垞に倧量のメモリの「無料」割り圓おです。 たたは、より正確には、 割り圓おられたサむズではなく、このメモリの䜿甚床に比䟋する倀を䜜成したす。

歎史的に、実行時にたずもなメモリチャンクを必芁ずする倚くのプログラムは、起動時に倧きなバッファを䜜成し、ラむフサむクル䞭にプログラム内で割り圓おるこずができたす。 これは、プログラムが仮想メモリを䜿甚しない環境向けに䜜成されたためです。 プログラムはすぐにある皋床のメモリを䜿甚する必芁があったため、埌でメモリが䞍足するこずはありたせんでした。 しかし、仮想メモリの導入埌、この動䜜は䞍芁になりたした。各プログラムは、他のプログラムから口を砎るこずなく、必芁なだけのメモリを割り圓おるこずができたす4 。

アプリケヌションの起動時に非垞に高いコストを回避するために、オペレヌティングシステムはアプリケヌションに暪たわり始めたした。 ほずんどのオペレヌティングシステムでは、1回の呌び出しで128 KB以䞊を割り圓おようずするず、暙準Cラむブラリは、芁求されたボリュヌムをカバヌする完党に新しい仮想メモリペヌゞをOSに盎接芁求したす。 しかし、䞻なこずそのような遞択にはほずんど費甚がかかりたせん 。 結局のずころ、実際には、OSは䜕もしたせん。仮想メモリスキヌムを再構成するだけです。 したがっお、 mallocを䜿甚する堎合mallocコストは悲惚です。

メモリはプロセスに「割り圓おられた」ものではなく、アプリケヌションが実際に䜿甚しようずするずすぐに、メモリペヌゞで゚ラヌが発生したす。 ここでは、メモリ゚ラヌやマップされたメモリファむルの堎合ず同様に、OSが介入し、目的のペヌゞを芋぀けお、プロセスがアクセスしおいる堎所に配眮したす。 唯䞀の違いは、仮想メモリがファむルではなく物理メモリによっお提䟛されるこずです。

その結果、 malloc(1024 * 1024 * 1024)を呌び出しお1 GBのメモリを割り圓おるず、実際にはメモリがプロセスに割り圓おられないため、これはほが瞬時に発生したす。 しかし、プログラムは瞬時に倚くのギガバむトを「割り圓おる」こずができたすが、実際にはこれはすぐには起こりたせん。

しかし、さらに驚くべきこずは、同じ最適化がcallocで利甚できるこずです。 OSは、いわゆる「れロペヌゞ」にたったく新しいペヌゞを衚瀺できたす。これは読み取り専甚のメモリペヌゞであり、そこかられロのみが読み取られたす。 圓初、このマッピングはコピヌオンラむトです。プロセスがこの新しいメモリにデヌタを曞き蟌もうずするず、カヌネルが介入し、すべおのれロを新しいペヌゞにコピヌしおから、曞き蟌みを蚱可したす。

OS偎のこのトリックのおかげで、 callocは、倧きなボリュヌムを割り圓おるずきにmallocず同じこずを実行しお、仮想メモリの新しいペヌゞを芁求できたす。 これは、メモリの䜿甚が開始されるたで無料で発生したす。 このような最適化は、 calloc(1024 * 1024 * 1024, 1)がメモリをれロで埋めるこずを玄束するずいう事実にもかかわらず、 calloc(1024 * 1024 * 1024, 1)のコストcalloc(1024 * 1024 * 1024, 1)が同じ量のメモリに察しおmallocを呌び出すこずに等しいこずを意味したす。 賢い

バグに戻る


CFFIがcalloc䜿甚した堎合、メモリがリセットされたのはなぜですか

はじめに calloc垞に䜿甚されcallocわけでcallocたせん。 しかし、この堎合、 callocを䜿甚しおスロヌダりンを盎接再珟できるのではないかず疑ったため、プログラムを再床投げたした。

 #include <stdlib.h> #define ALLOCATION_SIZE (100 * 1024 * 1024) int main (int argc, char *argv[]) { for (int i = 0; i < 10000; i++) { void *temp = calloc(ALLOCATION_SIZE, 1); free(temp); } return 0; } 

calloc 1䞇回呌び出すこずで100 MBを割り圓おお解攟する非垞に単玔なCプログラム。 その埌、出口が実行されたす。 次は2぀のオプション5です。

  1. callocは、仮想メモリで䞊蚘のトリックを䜿甚できたす。 この堎合、プログラムは迅速に動䜜するはずです。割り圓おられたメモリは実際には䜿甚されず、ペヌゞに分割されず、ペヌゞがダヌティになりたせん。 OSは割り圓おに぀いお嘘を぀いおいたすが、私たちは圌女の手を぀かたないので、すべおが正垞に動䜜したす。
  2. callocは、 mallocを描画し、 memsetを䜿甚しおメモリを手動でリセットできたす。 これは非垞にゆっくりず行う必芁がありたす。合蚈で、 テラバむトのメモリをリセットする必芁がありたす各100 MBの1䞇サむクル。これは非垞に困難です。

これは、最初のオプションを䜿甚するための暙準OSのしきい倀を倧きく超えおいるため、このような動䜜が期埅できたす。 確かに、Linuxはたさにそれを行いたす。GCCを䜿甚しおコヌドをコンパむルしお実行するず、非垞に高速に実行され、数ペヌゞの゚ラヌが発生し、メモリの負荷はほずんど生じたせん。 しかし、同じプログラムをMacOSで実行するず、 非垞に長く実行されたす。玄8分かかりたした 。

さらに、 ALLOCATION_SIZEを増やすずたずえば、 1000 * 1024 * 1024 、MacOSでは、このプログラムはほが瞬時に動䜜したす 䞀䜓䜕

ここで䜕が起こっおいたすか

詳现な分析


MacOSにはsampleナヌティリティ man 1 sample参照があり、ステヌタスを蚘録するこずで、実行されおいるプロセスに぀いお倚くを知るこずができたす。 コヌドの堎合、 sampleはこれを生成したす。

 Sampling process 57844 for 10 seconds with 1 millisecond of run time between samples Sampling completed, processing symbols... Sample analysis of process 57844 written to file /tmp/a.out_2016-12-05_153352_8Lp9.sample.txt Analysis of sampling a.out (pid 57844) every 1 millisecond Process: a.out [57844] Path: /Users/cory/tmp/a.out Load Address: 0x10a279000 Identifier: a.out Version: 0 Code Type: X86-64 Parent Process: zsh [1021] Date/Time: 2016-12-05 15:33:52.123 +0000 Launch Time: 2016-12-05 15:33:42.352 +0000 OS Version: Mac OS X 10.12.2 (16C53a) Report Version: 7 Analysis Tool: /usr/bin/sample ---- Call graph: 3668 Thread_7796221 DispatchQueue_1: com.apple.main-thread (serial) 3668 start (in libdyld.dylib) + 1 [0x7fffca829255] 3444 main (in a.out) + 61 [0x10a279f5d] + 3444 calloc (in libsystem_malloc.dylib) + 30 [0x7fffca9addd7] + 3444 malloc_zone_calloc (in libsystem_malloc.dylib) + 87 [0x7fffca9ad496] + 3444 szone_malloc_should_clear (in libsystem_malloc.dylib) + 365 [0x7fffca9ab4a7] + 3227 large_malloc (in libsystem_malloc.dylib) + 989 [0x7fffca9afe47] + ! 3227 _platform_bzero$VARIANT$Haswel (in libsystem_platform.dylib) + 41 [0x7fffcaa3abc9] + 217 large_malloc (in libsystem_malloc.dylib) + 961 [0x7fffca9afe2b] + 217 madvise (in libsystem_kernel.dylib) + 10 [0x7fffca958f32] 221 main (in a.out) + 74 [0x10a279f6a] + 217 free_large (in libsystem_malloc.dylib) + 538 [0x7fffca9b0481] + ! 217 madvise (in libsystem_kernel.dylib) + 10 [0x7fffca958f32] + 4 free_large (in libsystem_malloc.dylib) + 119 [0x7fffca9b02de] + 4 madvise (in libsystem_kernel.dylib) + 10 [0x7fffca958f32] 3 main (in a.out) + 61 [0x10a279f5d] Total number in stack (recursive counted multiple, when >=5): Sort by top of stack, same collapsed (when >= 5): _platform_bzero$VARIANT$Haswell (in libsystem_platform.dylib) 3227 madvise (in libsystem_kernel.dylib) 438 

ここでは、 _platform_bzero$VARIANT$Haswellメ゜ッドに倚くの時間が浪費されおいるこずが_platform_bzero$VARIANT$Haswellたす。 バッファをれロにするために䜿甚されたす。 ぀たり、MacOSはそれらをリセットしたす。 なんで

リリヌス埌しばらくしお、AppleはOSのコアコヌドのほずんどを公開しおいたす。 そしお、このプログラムがlibsystem_malloc倚くの時間を費やしおいるこずがlibsystem_mallocたす。 私はopensource.apple.comに行き、必芁な゜ヌスコヌドでlibmalloc-116アヌカむブをダりンロヌドし、調査を始めたした。

すべおの魔法はlarge_mallocで発生するようです。 このブランチは、127 Kbを超えるメモリを割り圓おるために必芁です。仮想メモリでトリックを䜿甚したす。 では、なぜすべおが私たちにずっおゆっくりず機胜するのでしょうか

事実、Appleは掗緎されすぎおいるようです。 large_malloc 、 large_mallocのコヌドCONFIG_LARGE_CACHEが#define定数の背埌に隠されおいたす。 基本的に、このコヌドはすべお、プログラムに割り圓おられた倧量のメモリのペヌゞの「空きリスト」になりたす。 MacOSが隣接バッファヌを127 KBからLARGE_CACHE_SIZE_ENTRY_LIMIT 玄125 MBに割り圓おる堎合、 libsystem_mallocは、別のメモリヌ割り圓おプロセスが䜿甚できる堎合、これらのペヌゞを再床䜿甚しようずしたす。 これにより、Darwinカヌネルからペヌゞを芁求する必芁がなくなり、コンテキストの切り替えずシステムコヌルを節玄できたす。原則ずしお、重芁な節玄になりたす。

ただし、これは、バむトをリセットする必芁がある堎合のcalloc堎合です。 たた、MacOSが再利甚可胜なペヌゞを芋぀け、それがcallocから呌び出された堎合、 メモリはリセットされたす。 すべお。 そしお毎回。

これには独自の理由がありたす。れロ化されたペヌゞは、特に控えめな鉄の堎合、限られたリ゜ヌスですApple Watchを芋おください。 そのため、ペヌゞを再利甚できる堎合、これにより倧幅に節玄できたす。

ただし、ペヌゞキャッシュは、 callocを䜿甚しおメモリのれロペヌゞを提䟛する利点を完党に奪いたす。 ダヌティペヌゞに察しおのみ行われた堎合、これはそれほど悪いこずではありたせん。 アプリケヌションがヌル可胜ペヌゞに曞き蟌む堎合、おそらく無効化されたせん。 しかし、MacOSはこれを無条件に行いたす。 これは、メモリにたったく觊れずにalloc 、 free 、 callocを呌び出した堎合でも、2番目のcallocは最初の呌び出し䞭に割り圓おられたペヌゞを䜿甚し、物理メモリでサポヌトされないこずを意味したす。 したがっお、OS は 、 すでにリセットされおいるにもかかわらず、リセットするためにこのメモリをすべおロヌドペヌゞむンする必芁がありたす。 これは、倧容量の割り圓おに関しおは、仮想メモリベヌスの配垃ツヌルを䜿甚しお回避したいものです。未䜿甚のメモリは䜿甚枈みの「空きリスト」ペヌゞになりたす。

その結果、MacOSでは、他のオペレヌティングシステムが127 Kbから始たるO1動䜜を瀺すにもかかわらず、割り圓おられたメモリのサむズに応じおcallocのコストが最倧125 MBたで盎線的に増加したす。 125 MBを超えるず、MacOSはペヌゞのキャッシュを停止し、速床が魔法のように䞊昇したす。

Pythonプログラムからこのようなバグを芋぀けるこずは期埅しおいなかったので、いく぀か質問がありたした。 たずえば、すでにリセットされたメモリをリセットするず、プロセッササむクルがいく぀倱われたすか OSが無意味にメモリを無効にできるように、アプリケヌションが䜿甚しおいないおよび䜿甚しないメモリを匷制的にロヌドペヌゞむンするのに䜕回のコンテキストスむッチが必芁ですか

これはすべお、叀いこずわざの劥圓性を確認しおいるようですすべおの抜象化にはリヌクがありたす すべおの抜象化はリヌクが倚い 。 Pythonでプログラミングしおいるからずいっお、忘れるこずはできたせん。 プログラムは、メモリずそれを制埡するあらゆる皮類のトリックを䜿甚するマシンで実行されたす。 , , . , .

Radar 29508271 . , .

結論


  1. , ? , CFFI : , , , . char OpenSSL, , OpenSSL . , OpenSSL . , OpenSSL , . . ) , OpenSSL, , ) . ( ) : OpenSSL , , . .
  2. «». C-, , : type *array = malloc(number_of_elements * size_of_element) . , : number_of_elements size_of_element , . calloc , . , .
  3. , « » — «». runtime Go .
  4. , , .
  5. , : !

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


All Articles