reallocおよび遅延のストヌリヌ

単玔なマクロ



すべおは単玔なマクロで始たりたした近䌌コヌド

#define ADD_BYTE(C) do { \ if (offset == capa) { \ if (capa < 16) { \ capa = 16; \ } else { \ capa <<= 1; \ } \ buffer = realloc(buffer, capa); \ assert(buffer != NULL); \ } \ buffer[offset++] = (C); \ } while(0)
      
      







Cプログラミング蚀語に粟通しおいない人のために、この単玔なマクロは、バむトCを動的に割り圓おられたバッファヌバッファヌに远加したす。 蚘録の次の䜍眮は、offsetパラメヌタヌを䜿甚しお決定されたす。 バッファヌがいっぱいになるたびに、ボリュヌムが2倍になりたす最小サむズの16バむトから開始。



動的バッファにバむトを远加したす-これは、ほずんどすべおのプログラムで最も䞀般的な操䜜の1぀です文字列、配列などを操䜜するため。



しかし、再配垃戊略がいかに効果的であるかをどのように理解するのでしょうか この問題を耇雑さの芳点から芋お、reallocの耇雑さをONにするず、1バむトを远加するず平均 O1の耇雑さがすぐにわかりたす。これは非垞に良いこずです。



しかし、最悪の堎合の耇雑さは䜕ですかバッファぞのメモリの再割り圓おが必芁な堎合はどうでしょうか

匿名 これは良い戊略ですか 倧きなアレむ1ギガバむトなどのサむズを増やし始めるず、深刻なパフォヌマンスの問題が発生したす。 特に、バッファをスワップファむルから移動する必芁がある堎合は、結果を想像しおください。



私 うヌん...正盎なずころ、私はそれに぀いお考えたこずはありたせん...しかし、私はすべおがそれほど怖くないず確信しおいたす。 システムはこのタスクに正垞に察凊する必芁がありたす。



匿名 そしお、同じ指数戊略であっおも、接続された構造がより良い遞択肢であるように思えたす。



私 いいえ、時間の無駄です。



匿名 蚌拠



ああ、そうですか



さお、あなたは自分の立堎を正圓化する必芁がありたす。 すべおのプログラマず同様に、私はずおも怠け者です。 より正確には「 プログラマヌにふさわしいので 、私はずおも怠け者です。」 怠azineは、よりスマヌトで効率的になるための倧きなむンセンティブです。 繰り返しのタスクや日垞的なタスクを解決するのが面倒です。 もっずむンテリゞェントなもの 、できるだけ速い ものが必芁です 。 はい、それは時々 怠 sometimesず呌ばれるものです。 しかし、私の意芋では、これは効率に他なりたせん。



バッファを保存するためのブロックのリンクリストは、やや面倒な解決策です。 これが䞍可胜だず蚀っおいるのではありたせん 。 「 むンポッシブルはフランス語ではない 」ナポレオンはか぀お蚀った圌はひどく終わったが。 解決策は可胜ですが、面倒です。 サブアレむをコピヌしたり、ディスクに保存したりするには倚少の手間がかかりたす。 おそらく、各配列の先頭ぞのポむンタのむンデックスを維持する必芁がありたす。ブロックの先頭のアドレスず倚くの退屈なものを取埗するために、2を底ずする察数を取埗したす。



私の怠lazはここで最も歓迎されるこずを䜕かが教えおくれたす。 結局のずころ、小さなブロックに぀いおはたったく心配する必芁はありたせん。システムの仮想メモリが倧きなブロックを凊理したす。



それを理解したしょう。



ずころで、reallocずは䜕ですか



これは、Cラむブラリに実装されおいる䞀般的なPOSIX準拠の機胜です。 Linuxの堎合、これはlibc.soラむブラリであり、mallocおよびfree関連の関数reallocも含たれおいたす。



 nm -D /lib/x86_64-linux-gnu/libc.so.6 | grep -E "T (malloc|realloc|free)$" 000000000007e450 T free 000000000007e030 T malloc 000000000007e4e0 T realloc
      
      







本圓に興味がある人のために「T」は「テキストシンボル」を意味し、倧文字はこのシンボルが公開されお芋えるこずを瀺すために䜿甚されたす。 プログラムテキストセグメントはcode 、 デヌタセグメントは初期化デヌタ倉数、 bssセグメントは初期化されおいないデヌタ倉数、たたはデヌタ倉数であり、初期倀ずしおれロを受け取りたした。



メモリアロケヌタは、メモリの割り圓お、再割り圓お、および解攟に䜿甚されたすありがずう、Captain Obvious。 そのようなディストリビュヌタヌは数倚くありたす最も有名なのはバディアロケヌタヌです 。



玠晎らしくひどいsbrk呌び出しを䜿甚しお、独自の最も単玔なアロケヌタヌを実装できたす。これは、デヌタセグメントの最埌に空のスペヌスを远加するだけです。



 #include <sys/types.h> #include <unistd.h> #include <string.h> void *malloc(size_t size) { return sbrk(size); } void free(void *ptr) { /*   ? */ } /* :    (,  ) */ void *realloc(void *ptr, size_t size) { void *nptr = malloc(size); if (nptr == NULL) { return NULL; } // «old_size»    :) // (,      ) memcpy(nptr, ptr, old_size); free(ptr); return nptr; }
      
      







[泚。 Hacker Newsの読者の䞀人が正しく述べたように、ここでは、たずえばブロックが遞択された埌にブロックの先頭に曞き蟌むこずができるold_size倀が必芁です。 しかし、いいえ、reallocで゚ラヌが発生した堎合に゜ヌスブロックを解攟すべきではありたせんでした:)]



もちろん、このディスペンサヌはもう少し耇雑になり、memcpy呌び出しの数を制限するために耇雑なデヌタ構造が必芁になりたす。



倧きなブロックで䜕が起こるかを理解するには、Linux䞊のGlibcをより詳しく知る必芁がありたす。



Glibcの玹介



最新のglibcをダりンロヌドしお 、゜ヌスツリヌを調べるだけです。

興味深いmallocディレクトリが1぀ありたす。 その䞭のmalloc.cファむルを芋぀けお、゚ディタヌで開きたす。



 /*   : *        (>= 512 ),      FIFO (. .    ). *    (<= 64   )   ,      . *    ,        ,          ,   . *     (>= 128   )     ,   .  ,    ,    : http://gee.cs.oswego.edu/dl/html/malloc.html */
      
      







この郚分に最も関心がありたす「 非垞に倧きなリク゚ストデフォルトでは128 Kb以䞊の堎合、サポヌトされおいる堎合、システムはメモリマッチングを䜿甚したす 。」



128 Kbのしきい倀はmallopt関数によっお蚭定されたす。



 M_MMAP_THRESHOLD    ,   ,     ( ),   M_MMAP_THRESHOLD,     mmap(2),          sbrk(2).
      
      







そのため、私が蚀ったように、システム自䜓の仮想メモリサヌビスが倧きなブロックを凊理したす。



本質的に、これは次のこずを意味したす。



さお、mremapの知識を少し広げおみたしょう。



それで、私たちはman mremapに぀いお䜕を知っおいたすか



 mremap()      Linux. mremap()       .  ,     realloc(3).
      
      







ええ、 非垞に効果的なreallocです。 どのくらい効果的ですか



最初に、mmap、munmap、mremapがglibcラむブラリに蚘述されおいたす。 実際、゚ントリポむントのみ

 nm -D /lib/x86_64-linux-gnu/libc.so.6 | grep -E "(mmap|munmap|mremap)$" 00000000000e4350 W mmap 00000000000e9080 W mremap 00000000000e4380 W munmap
      
      







この堎合、デフォルトの゚ントリポむントは「匱い」文字であるこずに泚意しおください。 ぀たり、動的リンク䞭に他の誰かによっお䞊曞きされる可胜性がありたす。 䟋



 ldd /tmp/sample linux-vdso.so.1 (0x00007584a8aaa000) libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007584a86e6000) /lib64/ld-linux-x86-64.so.2 (0x00007584a8aac000
      
      







...文字はlinux-vdso.so.1ラむブラリでオヌバヌラむドできたす-これはすべおのLinuxプログラムに衚瀺される魔法のラむブラリで、 システムコヌルを含む䞀郚のコヌルを高速化できたす。

いずれにせよ、glibcラむブラリのキャラクタヌは、システムカヌネルsyscallを呌び出すためのチャネルであり、glibcたたはvdsoになりたすデフォルトの実装を参照しおくださいsysdeps / unix / sysv / linux / mmap64.c。 䟋



 void * __mmap64 (void *addr, size_t len, int prot, int flags, int fd, off64_t offset) { //    void *result; result = (void *) INLINE_SYSCALL (mmap2, 6, addr, len, prot, flags, fd, (off_t) (offset >> page_shift)); return result; }
      
      







したがっお、最初の質問はglibcではなく、Linuxカヌネルに関連しおいたす。



コアを知る



最新のカヌネルバヌゞョンをダりンロヌドし、 mremapの仕組みを簡単に芋おみたしょう。



マニュアル The Linux Kernel Howto を芋るず、非垞に興味深いディレクトリが芋぀かりたす



mmディレクトリには、メモリを操䜜するために必芁なすべおのコヌドが含たれおいたす。 アヌキテクチャ固有のメモリ管理コヌドは、arch / * / mm /ずいう圢匏のディレクトリに配眮されたす䟋arch / i386 / mm / fault.c。




玠晎らしい。 それらが必芁です

ここに興味深いファむルがありたすmm / mremap.c。 その䞭に、mremap関数ぞのシステムコヌルの゚ントリポむントがありたす。 ここに



 /* *  ( )  , ,    * (    MREMAP_MAYMOVE    VM) * *  MREMAP_FIXED  5  1999 .   (Benjamin LaHaise) *     MREMAP_MAYMOVE. */ SYSCALL_DEFINE5(mremap, unsigned long, addr, unsigned long, old_len, unsigned long, new_len, unsigned long, flags, unsigned long, new_addr) {
      
      







glibcたたは察応するvdsoチャネルを介しおナヌザヌコヌドで察応するシステムコヌルを行うずきに 、カヌネルに入りたす。



この関数のコヌドを孊習するず、さたざたな匕数のチェックず些现なケヌスの凊理がわかりたすたずえば、メモリブロックの削枛 -ブロックの最埌でペヌゞを解攟するだけです。



次に、カヌネルは衚瀺領域を拡倧しお拡倧しようずしたすsbrkを䜿甚したCラむブラリ内のアロケヌタヌも同様です。 拡匵が成功した堎合、関数は結果を返したす。



これらの些现なケヌスはすべおO1の耇雑さで実装されおいたすカヌネルに入るコストを考慮しおも、割り蟌みは䜿甚されなくなりたすが、それでも䜿甚されたす。



そしお、 最悪のシナリオは䜕ですか

  /* *         *       . */ ret = -ENOMEM; if (flags & MREMAP_MAYMOVE) { unsigned long map_flags = 0; if (vma->vm_flags & VM_MAYSHARE) map_flags |= MAP_SHARED; new_addr = get_unmapped_area(vma->vm_file, 0, new_len, vma->vm_pgoff + ((addr - vma->vm_start) >> PAGE_SHIFT), map_flags); if (new_addr & ~PAGE_MASK) { ret = new_addr; goto out; } map_flags = vma->vm_flags; ret = move_vma(vma, addr, old_len, new_len, new_addr); if (!(ret & ~PAGE_MASK)) { track_exec_limit(current->mm, addr, addr + old_len, 0UL); track_exec_limit(current->mm, new_addr, new_addr + new_len, map_flags); } }
      
      







䞀芋するず、カヌネルは単玔なアロケヌタヌず同じこずを行いたす。



しかし、詳しく芋おみたしょう。



move_vmaの呌び出しは次のずおりです。

 static unsigned long move_vma(struct vm_area_struct *vma, unsigned long old_addr, unsigned long old_len, unsigned long new_len, unsigned long new_addr) { ... new_pgoff = vma->vm_pgoff + ((old_addr - vma->vm_start) >> PAGE_SHIFT); new_vma = copy_vma(&vma, new_addr, new_len, new_pgoff, &need_rmap_locks); if (!new_vma) return -ENOMEM; moved_len = move_page_tables(vma, old_addr, new_vma, new_addr, old_len, need_rmap_locks);
      
      







ここには2぀の興味深い課題がありたす。



copy_vma関数は、mm / mmap.cファむルに蚘述されおいたす。 タむプvm_area_structの構造を移動したす-これは、メモリのブロックを蚘述するカヌネルの内郚構造です。



定矩はここにありたすinclude / linux / mm_types.h。 この小さな構造には、領域に関するすべおの情報が含たれたす開始アドレスず終了アドレス、ディスク䞊のファむルファむルを衚瀺するためにメモリ領域が䜿甚される堎合など。



したがっお、この移動操䜜は非垞に簡単です-O1。



move_page_tables関数は䜕をしたすか



最も興味深いのはこのルヌプにあるようです



 ... for (; old_addr < old_end; old_addr += extent, new_addr += extent) { ... move_ptes(vma, old_pmd, old_addr, old_addr + extent, new_vma, new_pmd, new_addr, need_rmap_locks); ...
      
      







このコヌドは少し耇雑ですさらに、コメントはありたせんが、基本的にこれらは基本的な算術挔算ず蚈算です。



move_ptes関数には、最䜎レベルの内郚ルヌプが含たれたす。



 ... for (; old_addr < old_end; old_pte++, old_addr += PAGE_SIZE, new_pte++, new_addr += PAGE_SIZE) { if (pte_none(*old_pte)) continue; pte = ptep_get_and_clear(mm, old_addr, old_pte); pte = move_pte(pte, new_vma->vm_page_prot, old_addr, new_addr); ... set_pte_at(mm, new_addr, new_pte, pte); } ...
      
      







それでは、このサむクルで䜕が起こるのでしょうか メモリ領域に察応するペヌゞテヌブルの行 PTE 、 ペヌゞテヌブル゚ントリ を別の堎所に移動するだけです。 本質的に、これらは各ペヌゞに割り圓おられた敎数です。



それで、私たちには䜕がありたすか。実際、カヌネルはマップされたブロックのデヌタに觊れたせんでした 。 ゚リア党䜓を移動するには、数ビットを亀換するだけで十分でした。

正匏には、耇雑さはただ ONですが、



したがっお、ONを䜿甚したすが、倧きな違いがありたす。



ああ、ずころで、ONが倚くの堎合玛らわしいこずを知っおいたすか



この堎合、Nの最倧倀は2 48 仮想空間の最倧サむズです。 ほずんどのコンピュヌタヌはわずか数ギガバむトのメモリで動䜜したす-ほずんどのアヌキテクチャでは64 GBを超えたせん぀たり、2 36バむト。 ビッグペヌゞは2 21バむトで、move_ptesの操䜜の最倧数は2 15ですはい、これは 32,768操䜜のみです。



したがっお、最悪の堎合、コストは垞に極めお䜎く、䜕らかの理由で最初からそれを疑いたせんでした。



たた、読むこずをお勧めしたすMel GormanによるLinux Virtual Memory Managerの本を理解する 。



ナむアシリル・ムノガブカフ 心配しないで。 怠azineずreallocがすべおです。



All Articles