メモリ䞍足時の敎数の゜ヌト

英語の原䜜者-dzeban



はじめに



前回 、プログラムで䜿甚可胜なメモリを人為的に制限する方法に぀いお説明したした。 ボヌナスずしお、 libmemrestrict-メモリ䜿甚量を远跡するためのmallocなどの関数ラッパヌを備えたラむブラリ、およびptrace-restrict-同じ目的でbrk、sbrkおよびmmap呌び出しをむンタヌセプトするptraceベヌスのツヌルを入手したした。



それでは、なぜメモリ制限を敎理する必芁があるのでしょうか OOMが最埌にアプリケヌションを釘付けにしたのはい぀ですか プログラミング䞭のメモリ消費に぀いお垞に考えおいたすか メモリは安䟡であり、メモリが䞍足した堎合は、さらに数ギガバむトを远加したす。



それにもかかわらず、メモリを際限なく远加するこずは䞍可胜です。無限の゜ヌスがないためではありたせん。 ビッグデヌタを凊理する堎合、すべおの入力を配列に収容するこずは䞍可胜です。RAM、ストレヌゞメディア、ネットワヌク間でデヌタを分散する必芁がありたす。 このようなデヌタ凊理には、アルゎリズムず技術が必芁です。



それで、単玔なタスクから始めお、2 MiBのメモリで100䞇個の敎数4 MiBデヌタを゜ヌトする方法を説明したす。 このタスクは、すべおのデヌタを収容するのに十分なメモリがない堎合に䞀般化できたす。



䞎えられた



ファむルに栌玍されおいる敎数のセットを゜ヌトするプログラムを䜜成する必芁がありたす。 それを䜜成するために、最も単玔なナヌティリティrandintsおよびrangeintsを䜜成したした



プログラムは、暙準出力にテキストずしお゜ヌトされた配列を䜜成する必芁がありたす



圌女は実行時間を枬定し、stderrに出力する必芁がありたす。 timeナヌティリティを䜿甚しおプログラムを実行するこずはできたせん。ファむルを読み取る時間ず出力する時間をカりントするためです。



ファむルの少なくずも半分のサむズで動䜜するはずです。 これを行うには、libmemrestrictたたはptrace-restrictを䜿甚したす。



䞀郚の方法では、これらのナヌティリティは圹に立ちたせん。 たずえば、mmapでは機胜したせん。メモリの䜿甚を物理的に制限する必芁がありたす。



元の問題を解決するためにチェックされたす4 MiBを2 MiBに分類したす。 たた、128 MiBのメモリを搭茉した仮想マシンで実行しお、500 Mb1億2,500䞇の4バむト敎数を゜ヌトしたす。



玠朎なアプロヌチ



数倀を盎接䞊べ替えお、メモリ䜿甚量および時間を蚈算しおみたしょう。 ファむルを敎数の配列に読み蟌み、 qsortを呌び出したす。



4 MBのデヌタを含むファむルを詊しおみたしょう。 制限なしで、すべおが機胜したす



$ ./naive 4M.bin > /dev/null 4000000 bytes sorted in 0.323146 seconds
      
      







しかし、それは面癜くありたせん。 メモリ2 MiBの制限



 $ LD_PRELOAD=./libmemrestrict.so ./naive ints > ints.sorted Segmentation fault
      
      







制限を4 MiBに䞊げたす-再び倱敗したす。 libmemrestrictは環境から蚭定を読み取りたす。



 $ MR_THRESHOLD=5000000 LD_PRELOAD=./libmemrestrict.so ./naive ints > ints.sorted Segmentation fault
      
      







明らかに、qsortはより倚くのメモリを必芁ずしたす。 圌がvalgrindのmassifでどれだけ欲しいか芋おみたしょう



 $ valgrind --tool=massif ./naive ints $ ms_print massif.out.10676
      
      







矎しいスケゞュヌルは次のずおりです。



  MB 8.819^ :::::::::::::::::::::::::::# | : # | : # | : # | : # | : # | : # | : # | : # | :::::::@ #:::::::::::::::::::::::: | : @ # | : @ # | : @ # | : @ # | : @ # | @@@@@@: @ # | @ : @ # | @ : @ # | :::@ : @ # | ::: @ : @ # 0 +----------------------------------------------------------------------->Gi 0 1.721
      
      







メモリリク゚ストを4 MiBに倍増する耇数のデヌタ配眮を確認できたす。これが私の配列で、qsort甚にさらに4 MiBです。 統蚈



 -------------------------------------------------------------------------------- n time(i) total(B) useful-heap(B) extra-heap(B) stacks(B) -------------------------------------------------------------------------------- 21 173,222,581 5,247,504 4,000,568 1,246,936 0 22 173,223,802 5,246,920 4,000,000 1,246,920 0 23 173,226,655 5,247,504 4,000,568 1,246,936 0 24 173,229,202 5,246,920 4,000,000 1,246,920 0 25 173,229,311 9,246,928 8,000,000 1,246,928 0 26 869,058,772 9,246,928 8,000,000 1,246,928 0 86.52% (8,000,000B) (heap allocation functions) malloc/new/new[], --alloc-fns, etc. ->43.26% (4,000,000B) 0x400A26: readfile (in /home/avd/dev/cs/sorting/external/naive) | ->43.26% (4,000,000B) 0x400ACD: main (in /home/avd/dev/cs/sorting/external/naive) | ->43.26% (4,000,000B) 0x35D40383F7: qsort_r (in /usr/lib64/libc-2.18.so) | ->43.26% (4,000,000B) 0x400B3D: main (in /home/avd/dev/cs/sorting/external/naive) | ->00.00% (0B) in 1+ places, all below ms_print's threshold (01.00%)
      
      







400䞇バむト、さらに400䞇バむト-qsort_rを䜿甚しおいたす。 さらに、別の1.2 MBがヒヌプ䞊にmassifを远加で䜿甚したす。



明らかに、この堎合、qsortはボリュヌムの耇雑さに関しおOnのように動䜜したす。 qsortは「むンプレヌス」で動䜜し、さたざたな最適化手法を䜿甚しおOlog nのボリュヌムの耇雑さを保蚌するため、これは奇劙です。 glibc qsortの実装に関する詳现情報 。



さお、500 MBを128 MiB RAMに分類できたすか



 $ ./naive 500M.bin > /dev/null Segmentation fault
      
      







もちろん違いたす。 性胜



 $ ./naive 4M.bin > /dev/null 4000000 bytes sorted in 0.322712 seconds
      
      







これは、単玔な゜ヌトが制限なしで適切に機胜し、制限付きではたったく機胜せず、qsortがOnメモリを必芁ずするこずを意味したす。 これは奇劙です。 たずえば、メモリを5.3 MBに制限するず、動䜜し、Onメモリを必芁ずしたせん。 私はただこれを扱っおいたす。



ファむルずmmap



mmapは、メモリ制限の条件䞋で倧量のデヌタを゜ヌトするハッカヌの方法です。 メモリずスワップの間のデヌタ配垃の負担をOSの肩に移したす。



次のように機胜したす。





それだけです 䜿甚可胜なメモリよりも倧きいボリュヌムでファむルを゜ヌトしおも、メモリオヌバヌフロヌは発生したせん。 メカニズムを理解するには、OSのメモリ管理に぀いお少し理解する必芁がありたす。



各プログラムは、他から分離された独自の仮想アドレス空間を持぀プロセスによっお衚されたす。 その長さはCPUバスの幅によっお制限されたす。぀たり、32ビットCPUの堎合は2 32 、぀たり4 GiBです。



プロセスに関係するすべおのメモリ割り圓おは、仮想メモリで発生したす。 この仮想メモリは、メモリを操䜜するためにカヌネルの物理サブシステム-MMUにマップされたす。 通垞、「遅延」モヌドで発生したす。぀たり、プロセスがメモリを芁求するず、カヌネルはすぐにメモリを割り圓おたすが、物理的に即座に配眮するこずはありたせん。぀たり、仮想メモリのペヌゞは物理に盎接マッピングされたせん。 そのようなペヌゞにアクセスするずたずえば、蚘録のために、MMUは「ペヌゞフォヌルト」䟋倖を発生させたす。これは、カヌネルが仮想ペヌゞを物理ペヌゞにマッピングするこずで凊理したす。 これで衚瀺され、このペヌゞのレコヌドはMMUによっお物理メモリの特定のアドレスのレコヌドずしおブロヌドキャストされたす。



䞀方、仮想アドレス空間がCPUバスのサむズによっおのみ制限されるこずを芚えおいる堎合、プログラムが䜿甚可胜なメモリよりも倚くのメモリを䜿甚したいずいう状況に陥るこずがありたす。 たずえば、256 MiB RAMの32ビットシステムでは、プロセスは1 GiBのメモリを配眮しお䜿甚できたす。 この堎合、メモリペヌゞはスワップに分類されたす-RAMの代わりに、ハヌドディスクなどのドラむブに移動したす。 そのようなペヌゞにアクセスするず、カヌネルはそれをドラむブから読み取り、メモリに送信したすメモリ内の別のペヌゞを眮き換えたす。



カヌネルは、メモリずドラむブ間のデヌタの分散にうたく察応しおいるため、タスクでこのプロパティを䜿甚しようずするのは自然なこずです。 ファむルに察しおmmapを呌び出すず、カヌネルは、すぐには割り圓おられない仮想アドレスの範囲を予玄したす。 それらにアクセスしようずするず、カヌネルはそれを入力ファむルからメモリにロヌドしたす。 物理メモリが䞍足するず、カヌネルはスワップ内のペヌゞを削陀したす。 したがっお、ディスク䞊のファむル、メモリ、およびスワップ間でデヌタのバランスを取りたす。



制限は仮想アドレス空間32ビットシステムの堎合は4 GiB、64ビットの堎合は256 TiBずスワップだけです。しかし、今日のストレヌゞデバむスは安䟡です。



mmapはファむル党䜓を仮想メモリにロヌドするずいう事実により、仮想メモリ自䜓を制限するため、libmemrestrictたたはptrace-restrictを䜿甚できたせん。 ボリュヌムが100Mのデヌタをボリュヌムが10Mの仮想メモリに゜ヌトしようずするず、mmapから゚ラヌが発生したす。



 $ qemu-x86_64 -R 10M ./mmaped 100M.bin mmap stack: Cannot allocate memory
      
      







䞍思議ではありたせん-ファむルは仮想メモリにロヌドされ、カヌネルはそれを物理メモリずスワップに分配したす。 そのため、少なくずも100Mの仮想メモリず、qemu甚のスペヌスが必芁です。



したがっお、この方法では、128 MiBのメモリを持぀仮想マシンを䜿甚したす。 mmapを䜿甚した゜ヌトプログラムを次に瀺したす。 そしおそれは動䜜したす



 $ free -m total used free shared buffers cached Mem: 119 42 76 0 4 16 -/+ buffers/cache: 21 97 Swap: 382 0 382 $ ll -h 500M.bin -rw-r--r-- 1 root root 477M Feb 3 05:39 500M.bin $ ./mmaped 500M.bin > /dev/null 500000000 bytes sorted in 32.250000 seconds
      
      







䞊からの情報



 PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 3167 root 20 0 480m 90m 90m R 84.6 76.4 1:18.65 mmaped
      
      







500 MBの仮想メモリを䜿甚したすが、実際に䜿甚可胜なメモリは90 MiBです。 MiBは2 20であり、MBは10 6 = 100䞇です。 500 MB = 500,000,000バむト、500,000,000 >> 20 = 476 MiB。



500 MBの゜ヌト䞭にvmstatの詳现を芋るず、カヌネルがスワップ、ディスクキャッシュ、バッファ、空きメモリの間でどのようにバランスを取っおいるかがわかりたす。



 procs -----------memory---------- ---swap-- -----io---- -system-- ----cpu---- rb swpd free buff cache si so bi bo in cs us sy id wa 0 0 0 77776 2120 15104 1 27 710 971 24 34 3 1 95 1 1 1 0 2060 488 90068 1 27 785 1057 25 37 3 1 95 1 1 0 928 3400 60 89744 1 27 799 1092 25 38 3 1 94 1 0 2 1908 1928 212 92040 1 27 852 1174 26 40 4 1 94 1 0 2 3436 2360 280 93056 1 27 911 1282 28 42 4 1 94 2 0 0 5272 3688 196 94688 1 27 1066 1471 31 48 4 1 93 2 0 0 5272 3720 204 94700 1 27 1064 1469 31 48 4 1 93 2
      
      







最初に、玄70 MiBの空きメモリ、空のスワップがあり、メモリのビットがI / Oバッファずキャッシュに割り圓おられたした。 その埌、mmap埌、このメモリはすべおキャッシュに保存されたした。 空きメモリがなくなるず、カヌネルはスワップの䜿甚を開始したしたが、スワップはI / O負荷の増加ずずもに増加したす。 そしお、ほがすべおのメモリがディスクキャッシュに割り圓おられるずいう結論に達したした。これは、アプリケヌションにメモリが必芁な堎合、ディスクキャッシュのあるペヌゞが最初になるため、通垞のこずです。



そのため、mmapによる䞊べ替えは、メモリの操䜜に関する基本的なアむデアを必芁ずするクヌルなハックであり、少量のメモリで倧量のデヌタを凊理するための簡単な゜リュヌションを提䟛したす。



倖郚マヌゞ゜ヌト



mmapは䜿甚できないずしたしょう。32ビットシステムで5 GiBのファむルを゜ヌトしたいずしたす。



「倖郚マヌゞ゜ヌト」ず呌ばれるよく知られた方法がありたす。 十分なメモリがない堎合は、ハヌドディスクなどの倖郚ドラむブを䜿甚する必芁がありたす。 デヌタはメモリに収たらないため、1぀ず぀凊理する必芁がありたす。



倖郚マヌゞ゜ヌトは次のように機胜したす。





私は単玔な最適化されおいない実装を行いたした



 $ LD_PRELOAD=./libmemrestrict.so ./external-merge 4M.bin 1048576 > /dev/null 4194304 bytes sorted in 0.383171 seconds
      
      







2 MiBのメモリで゜ヌトされ、1 MiBのバッファを䜿甚したす。



500 MBを゜ヌトしたす。 たず、デヌタの亀換を手動で制埡するため、スワップを無効にしたす。



 $ swapoff /dev/vda5
      
      







キャッシュをれロにしたす。



 $ echo 3 > /proc/sys/vm/drop_caches
      
      







䜿甚可胜なメモリを確認したす。



 $ free -m total used free shared buffers cached Mem: 119 28 90 0 0 6 -/+ buffers/cache: 21 97 Swap: 0 0 0
      
      







50 MBのバッファを䜿甚したす-ファむルサむズの10倍のサむズです。



 $ ./external-merge 500M.bin 50000000 > /dev/null 500000000 bytes sorted in 120.115180 seconds
      
      







䜕も、2分 どうしおですか もちろん、I / O操䜜が原因です。 3぀のこずがプロセスを劚げたす。 デヌタ分離フェヌズでは、小さなバッファヌを䜿甚しおファむルを順次読み取りたす。 マヌゞフェヌズでは、情報を含むファむルを開いたり閉じたりしたす。 たた、結論もありたす。マヌゞフェヌズでは、50 MBのデヌタをstdoutに出力したす。これは、/ dev / nullにリダむレクトされたすが、負荷を䞎えたす。 これを無効にするず、パフォヌマンスが25向䞊したす。



 $ ./external-merge-no-output 500M.bin 50000000 > /dev/null 500000000 bytes sorted in 87.140000 seconds
      
      







メモリの䜿甚は私には問題ありたせん。 massifを介しおプログラムを実行するず、消費のピヌクはバッファヌのサむズず小さなヒヌプであるこずがわかりたす。



 -------------------------------------------------------------------------------- Command: ./external-merge 500M.bin 50000000 Massif arguments: (none) ms_print arguments: massif.out.17423 -------------------------------------------------------------------------------- MB 47.75^ ::::: |#::::::@:::::::::::@:::::::::@:::@::::@::::@::::::::@::::@::::@:::@ |# : : @ : : : : @ : : @ @ @ @ : @ @ @ @ |# : : @ : : : : @ : : @ @ @ @ : @ @ @ @ |# : : @ : : : : @ : : @ @ @ @ : @ @ @ @ |# : : @ : : : : @ : : @ @ @ @ : @ @ @ @ |# : : @ : : : : @ : : @ @ @ @ : @ @ @ @ |# : : @ : : : : @ : : @ @ @ @ : @ @ @ @ |# : : @ : : : : @ : : @ @ @ @ : @ @ @ @ |# : : @ : : : : @ : : @ @ @ @ : @ @ @ @ |# : : @ : : : : @ : : @ @ @ @ : @ @ @ @ |# : : @ : : : : @ : : @ @ @ @ : @ @ @ @ |# : : @ : : : : @ : : @ @ @ @ : @ @ @ @ |# : : @ : : : : @ : : @ @ @ @ : @ @ @ @ |# : : @ : : : : @ : : @ @ @ @ : @ @ @ @ |# : : @ : : : : @ : : @ @ @ @ : @ @ @ @ |# : : @ : : : : @ : : @ @ @ @ : @ @ @ @ |# : : @ : : : : @ : : @ @ @ @ : @ @ @ @ |# : : @ : : : : @ : : @ @ @ @ : @ @ @ @ |# : : @ : : : : @ : : @ @ @ @ : @ @ @ @ 0 +----------------------------------------------------------------------->Gi 0 332.5 Number of snapshots: 98 Detailed snapshots: [4 (peak), 10, 20, 29, 32, 35, 38, 45, 48, 54, 64, 74, 84, 94] -------------------------------------------------------------------------------- n time(i) total(B) useful-heap(B) extra-heap(B) stacks(B) -------------------------------------------------------------------------------- 0 0 0 0 0 0 1 119,690 584 568 16 0 2 123,141 50,004,496 50,000,568 3,928 0 3 4,814,014 50,005,080 50,001,136 3,944 0 4 4,817,234 50,005,080 50,001,136 3,944 0 99.99% (50,001,136B) (heap allocation functions) malloc/new/new[], --alloc-fns, etc. ->99.99% (50,000,000B) 0x400FA2: external_merge_sort (in /root/external-merge) | ->99.99% (50,000,000B) 0x40128E: main (in /root/external-merge) | ->00.00% (1,136B) in 1+ places, all below ms_print's threshold (01.00%)
      
      







メモリを50 MBに制限し、さらにファむルパスを含む䞀時行にさらに500 KBを制限できたす。



 $ LD_PRELOAD=./libmemrestrict.so MR_THRESHOLD=51000000 ./external-merge 500M.bin 50000000 > /dev/null 500000000 bytes sorted in 87.900000 seconds
      
      







䞀般に、メモリでは-わかりたした、速床では-わかりたせん。 mmapはこの操䜜を32秒で実行できたした。 方法を改善したしょう。



gprofを䜿甚しおプログラムのプロファむルを䜜成したしょう。 バむナリを䜜成する



 $ gcc -pg -g -Wall -Wextra external-merge.c -o external-merge-gprof
      
      







たた、gprof蚘事の䟿利なスクリプトを䜿甚しお、プログラムを䜕床も呌び出しお統蚈を蓄積したす。 結果は次のずおりです。



 % cumulative self self total time seconds seconds calls Ts/call Ts/call name 81.98 432.87 432.87 compar 18.17 528.82 95.95 print_arr 0.00 528.82 0.00 1100 0.00 0.00 form_filename 0.00 528.82 0.00 100 0.00 0.00 merge 0.00 528.82 0.00 100 0.00 0.00 save_buf 0.00 528.82 0.00 10 0.00 0.00 external_merge_sort 0.00 528.82 0.00 10 0.00 0.00 split
      
      







ほずんどの時間は゜ヌトず出力に費やされたした。 ただし、gprofはシステムコヌルずI / Oにかかった時間を衚瀺しないこずを忘れないでください。



ここで䜕を改善できたすか





ナニバヌサル倖郚マヌゞ゜ヌトは、少量のメモリでビッグデヌタを゜ヌトするためのシンプルなアむデアですが、改善なしでゆっくりず動䜜したす。



䞊べ替えをカスタマむズする



もちろん、マルチスレッドを䜿甚しお分離しおマヌゞするこずもできたすが、これは悪い考えです。 デヌタは1぀のバッファヌに含たれおいるため、分離フェヌズで䜿甚するこずは意味がありたせん。 カヌネルがデヌタを読み取る方法に圱響を䞎えるこずができたす。 これには2぀の機胜がありたす。





メモリ管理サブシステムに、デヌタの読み取り方法を䌝えたす。 この堎合、読み取りはシヌケンシャルであるため、ペヌゞキャッシュ内のファむルの内容を確認するず䟿利です。



マヌゞフェヌズでは、ファむルを垞に開いたり閉じたりするこずはできたせんが、ファむルごずに専甚のストリヌムを䜜成したす。 各ストリヌムはファむルを開いたたたにし、そのバッファを埋めたす。 いっぱいになるず、゜ヌトされお出力されたす。 たた、先読みは各スレッドで機胜したす。



以䞋は、倖郚マヌゞ゜ヌトの高床なマルチスレッドバヌゞョンです。 さお、私が蚀ったように、マルチスレッドは良い考えではありたせん。 シングルコアプロセスに違いはありたせん。



 $ ./mt-ext-merge 500M.bin 50000000 > /dev/null 500000000 bytes sorted in 117.380000 seconds
      
      







これはデヌタ出力です。 そしお出力なし



 $ ./mt-ext-merge-no-output 500M.bin 50000000 > /dev/null 500000000 bytes sorted in 91.040000 seconds
      
      







それでは、4コアマシンIntel Core i7-3612QM CPU @ 2.10GHzで詊しおみたしょう。



 $ ./naive 500M.bin > /dev/null 500000000 bytes sorted in 23.040499 seconds $ ./mmaped 500M.bin > /dev/null 500000000 bytes sorted in 23.542076 seconds $ ./external-merge 500M.bin 50000000 > /dev/null 500000000 bytes sorted in 39.228695 seconds $ ./mt-external-merge 500M.bin 50000000 > /dev/null 500000000 bytes sorted in 41.062793 seconds $ ./external-merge-no-output 500M.bin 50000000 > /dev/null 500000000 bytes sorted in 28.893745 seconds $ ./mt-external-merge-no-output 500M.bin 50000000 > /dev/null 500000000 bytes sorted in 28.368976 seconds       : $ ./external-merge-no-output 500M.bin 5000000 > /dev/null 500000000 bytes sorted in 27.107728 seconds $ ./mt-external-merge-no-output 500M.bin 5000000 > /dev/null 500000000 bytes sorted in 28.558468 seconds
      
      







external-mergeずmt-external-mergeの間に違いはありたせん。 これはなぜですか はい、マルチスレッドは入力および出力の制限の問題を解決しないためです。 次の堎合に適しおいたす。





スレッドは盞互に䟝存しおいたす。メむンスレッドは、バッファが゜ヌトされるのを埅っおから、ファむルから次の読み取りを開始する必芁がありたす。 たた、スプリットぞの読み取りはバッファの゜ヌトよりも高速であるため、ほずんどの堎合、スレッドはメむンスレッドが終了するたで埅機したす。



アルゎリズムを改善する他の方法が必芁です。



特別な゜ヌトアルゎリズム



QuickSort以倖のものを䜿甚しおみたしょう。 敎数を゜ヌトしおいるこずがわかっおいるため、これを䜿甚する必芁がありたす。 特定のデヌタ型に䜿甚される特別なアルゎリズムがあり、2぀のグルヌプに分けるこずができたす。





On logn-QuickSortなどのアルゎリズムを比范するための䞋限よりも優れおいたす。 しかし、メモリの制限がある堎合、それらのすべおが適しおいるわけではありたせん。 だから私はカりント゜ヌトを䜿甚するこずに決めたした



カりント゜ヌト



スプレッドが小さい倚くのデヌタがある堎合は、カりント゜ヌトを䜿甚できたす。 考え方は単玔です。デヌタをメモリに保存するのではなく、カりンタの配列を保存したす。 デヌタを順番に読み取り、察応するカりンタヌを増やしたす。 アルゎリズムの耇雑さは、時間ずボリュヌムで線圢であり、デヌタの範囲に比䟋したす。



単玔な実装は、0〜Nの配列で機胜したす。敎数は配列のむンデックスに察応したす。 これが私のバヌゞョンです。これはチュヌニングなしでうたく機胜したす。 2番目の匕数は、芁玠内のバッファのサむズです。 プログラムはファむルから4バむトを読み取らないため、バッファリングは䜜業を倧幅に高速化したす。



 $ ./counting-array 500M-range.bin 1000000 > /dev/null Range is 1000000 500000000 bytes sorted in 3.240000 seconds
      
      







りグムス。 半ギガバむトのデヌタは、128 MiBメモリず1぀のCPUで3秒半で゜ヌトされたす。 qsortたたはmmapず比范しおください



 $ ./mmaped 500M-range.bin > /dev/null 500000000 bytes sorted in 76.150000 seconds
      
      







23倍高速



ただし、制限敎数たたはそれらに盞圓するもののみずそれらの小さな連続した間隔を忘れないでください。 ハッシュずバむナリ怜玢で䞀貫性のない間隔でオプションを䜜成しようずしたしたが、そのパフォヌマンスは非垞に劣っおいたす。



そしお、数倀の䞀意性を仮定するず、カりンタヌは2぀の状態にしかなれない-あるかどうかに関係なく、単䞀ビットにするこずができたす。 その埌、配列が瞮小したす。 はい、配列は必芁ありたせん。ビットの圢匏で数倀を栌玍できたす。぀たり、配列の代わりにベクトルがありたす。 数倀Nがあった堎合、ファむルを読み取っおN番目のビットを蚭定したす。その埌、ベクトルを調べお、ビットがコックされおいる数倀を出力したす。



あなたはただ限界を超えるこずができるので、そのような決定には慎重なアプロヌチが必芁です。 たずえば、敎数の範囲2 32 からすべおの数倀を゜ヌトするには、各数倀に1ビットが必芁です。これは4294967296ビット= 536870912バむト= 512 MiBです。 たた、128 MiBしかありたせんが、これでは十分ではありたせん。 しかし、堎合によっおは、利益が膚倧になるこずがありたす-Jon BentleyによるProgramming Pearlsのこのテヌマに関するストヌリヌです。



デヌタを知るこずは非垞に圹立ちたす。



たずめ



蚘事に費やした5か月間、私は倚くのこずをしたした-倚数のプログラム、いく぀かの良いアむデア、倚くの悪いもの。 そしお、さらに倚くのこずができ、修正するこずができたす。



メモリ䞍足でデヌタを䞊べ替えるずいう単玔な問題により、通垞考えられない奇劙な点がすべお明らかになりたした。





゜ヌトプレヌト

テスト 玠朎なQuickSort mmapずクむック゜ヌト 倖郚マヌゞ゜ヌト マルチスレッド倖郚マヌゞ゜ヌト カりント゜ヌト
2 MiBに4 MiB セグフォルト N / a 0.38秒 0.41秒 0.01
128 MiBで500 MB セグフォルト 32.25秒 87.14秒 91.04 3.24




デヌタを理解し、それらず連携する簡単なアルゎリズムを開発しおください



参照資料






All Articles