上記エントリにおいて、ページテーブルサイズによるcloneの性能劣化とCPU使用時間の専有問題について確認しました。そこで、システムコールやそのkernel内部の処理の性能、というよりは、より実践的な環境であるApache httpdとmod_cgiを用いて、phpinfo()を実行するだけのCGIに対してベンチマークをかけた時にどれぐらいCPUのidleが空くか、システムCPUの使用量が変わるかを、HugePagesを使うかどうかの観点で比較してみましょう。
まずはページサイズがデフォルト4KBのhttpd
弊社では超高集積なWebホスティングを提供しているので、httpd+mod_cgiにおいては、その超高集積を再現すべく10万vhostを作成しておいて、予め各preforkされたhttpdプロセスが4GB程度メモリを確保している状態を再現します。これは、上記エントリでcloneの速度を検証した時と同じ環境(CPUコア24個、メモリ32GB、256個のprefork MPM)になります。
- 以下のように普通に起動
/usr/sbin/httpd
で普通に起動した後、以下のベンチマークを実施します。このベンチマークによって、よくある高負荷状態を再現します。
ab -l -HUser-Agent:hoge -k -c 150 -n 10000000 http://test.example.jp/info.php
- ベンチマーク中の
top
そして、ベンチマーク中のCPU使用率をコア単位で見てみましょう。
top - 18:07:01 up 9 days, 13 min, 4 users, load average: 140.95, 88.39, 43.33 Tasks: 689 total, 151 running, 538 sleeping, 0 stopped, 0 zombie Cpu0 : 4.3%us, 95.7%sy, 0.0%ni, 0.0%id, 0.0%wa, 0.0%hi, 0.0%si, 0.0%st Cpu1 : 3.6%us, 95.7%sy, 0.0%ni, 0.0%id, 0.0%wa, 0.0%hi, 0.7%si, 0.0%st Cpu2 : 3.0%us, 96.7%sy, 0.0%ni, 0.0%id, 0.0%wa, 0.0%hi, 0.3%si, 0.0%st Cpu3 : 7.2%us, 92.4%sy, 0.0%ni, 0.0%id, 0.0%wa, 0.0%hi, 0.3%si, 0.0%st Cpu4 : 12.2%us, 87.5%sy, 0.0%ni, 0.0%id, 0.0%wa, 0.0%hi, 0.3%si, 0.0%st Cpu5 : 3.6%us, 96.1%sy, 0.0%ni, 0.0%id, 0.0%wa, 0.0%hi, 0.3%si, 0.0%st Cpu6 : 4.3%us, 95.7%sy, 0.0%ni, 0.0%id, 0.0%wa, 0.0%hi, 0.0%si, 0.0%st Cpu7 : 4.3%us, 95.7%sy, 0.0%ni, 0.0%id, 0.0%wa, 0.0%hi, 0.0%si, 0.0%st Cpu8 : 9.2%us, 90.8%sy, 0.0%ni, 0.0%id, 0.0%wa, 0.0%hi, 0.0%si, 0.0%st Cpu9 : 5.9%us, 94.1%sy, 0.0%ni, 0.0%id, 0.0%wa, 0.0%hi, 0.0%si, 0.0%st Cpu10 : 13.8%us, 86.2%sy, 0.0%ni, 0.0%id, 0.0%wa, 0.0%hi, 0.0%si, 0.0%st Cpu11 : 19.5%us, 80.5%sy, 0.0%ni, 0.0%id, 0.0%wa, 0.0%hi, 0.0%si, 0.0%st Cpu12 : 4.0%us, 96.0%sy, 0.0%ni, 0.0%id, 0.0%wa, 0.0%hi, 0.0%si, 0.0%st Cpu13 : 3.6%us, 96.1%sy, 0.0%ni, 0.0%id, 0.0%wa, 0.0%hi, 0.3%si, 0.0%st Cpu14 : 3.3%us, 96.7%sy, 0.0%ni, 0.0%id, 0.0%wa, 0.0%hi, 0.0%si, 0.0%st Cpu15 : 9.2%us, 90.5%sy, 0.0%ni, 0.0%id, 0.0%wa, 0.0%hi, 0.3%si, 0.0%st Cpu16 : 15.2%us, 84.5%sy, 0.0%ni, 0.0%id, 0.0%wa, 0.0%hi, 0.3%si, 0.0%st Cpu17 : 8.3%us, 91.4%sy, 0.0%ni, 0.0%id, 0.0%wa, 0.0%hi, 0.3%si, 0.0%st Cpu18 : 7.2%us, 92.8%sy, 0.0%ni, 0.0%id, 0.0%wa, 0.0%hi, 0.0%si, 0.0%st Cpu19 : 18.8%us, 81.2%sy, 0.0%ni, 0.0%id, 0.0%wa, 0.0%hi, 0.0%si, 0.0%st Cpu20 : 15.5%us, 84.5%sy, 0.0%ni, 0.0%id, 0.0%wa, 0.0%hi, 0.0%si, 0.0%st Cpu21 : 10.6%us, 89.4%sy, 0.0%ni, 0.0%id, 0.0%wa, 0.0%hi, 0.0%si, 0.0%st Cpu22 : 24.1%us, 75.9%sy, 0.0%ni, 0.0%id, 0.0%wa, 0.0%hi, 0.0%si, 0.0%st Cpu23 : 7.3%us, 92.7%sy, 0.0%ni, 0.0%id, 0.0%wa, 0.0%hi, 0.0%si, 0.0%st Mem: 32862764k total, 29007852k used, 3854912k free, 13300k buffers Swap: 16777212k total, 222652k used, 16554560k free, 199396k cached
このように、通常のページサイズではidleは0%
であり、特にシステムCPUの専有時間が90%
前後と非常に大きいことがわかります。
- perfでkernelのシンボルのCPU使用率を確認
続いて、ユーザランドとカーネルで実行されいてる処理のシンボルを統一的にみて、その使用率を確認します。
Samples: 12M of event 'cycles', Event count (approx.): 873395738882 21.55% [kernel] [k] zap_pte_range 20.55% [kernel] [k] copy_pte_range 5.72% [kernel] [k] change_pte_range 5.47% [kernel] [k] page_remove_rmap 5.00% [kernel] [k] free_pages_and_swap_cache 3.47% ld-2.12.so [.] do_lookup_x 3.16% [kernel] [k] release_pages 2.37% ld-2.12.so [.] strcmp 1.48% [kernel] [k] vm_normal_page 1.43% ld-2.12.so [.] _dl_map_object 1.13% ld-2.12.so [.] _dl_relocate_object 1.10% [kernel] [k] copy_page 1.04% [kernel] [k] __tlb_remove_page 0.91% [kernel] [k] clear_page_c_e 0.89% libc-2.12.so [.] _int_free 0.71% [kernel] [k] _raw_spin_lock 0.58% libc-2.12.so [.] _int_malloc
これまでの調査、及び、前回のエントリの調査のとおり、pte(ページテーブルエントリ)の数に比例して処理が多くなるシンボル(clone
から呼ばれるcopy_pte_range
やevecve
から呼ばれるzap_pte_range
など)が支配的であることがわかります。
これは、非常に実用上厳しい状況ですね。
HugePagesを使って起動したhttpdの場合
では、前回のエントリで調査したとおり、pteを少なくすることでclone
から呼ばれるcopy_pte_range
やevecve
から呼ばれるzap_pte_range
のコストを大幅に削減する事が原理的にわかりました。それが実環境やベンチマーク上でどのような効果があるかをより詳細に確認します。
- 以下のようにHugePagesを使ってhttpdを起動
HUGETLB_MORECORE=yes LD_PRELOAD=/usr/lib64/libhugetlbfs.so /usr/sbin/httpd
HugePagesが既に20GB確保されている状態で、上記のようにlibhugetlbfs経由で起動します。HugePagesの確保の仕方は前回のエントリの調査を参照してください。
次に同様のベンチマークを流します。
top
top - 15:51:57 up 8 days, 21:58, 3 users, load average: 87.35, 50.87, 28.25 Tasks: 599 total, 7 running, 592 sleeping, 0 stopped, 0 zombie Cpu0 : 9.6%us, 16.8%sy, 0.0%ni, 72.6%id, 0.3%wa, 0.0%hi, 0.7%si, 0.0%st Cpu1 : 19.1%us, 21.1%sy, 0.0%ni, 59.4%id, 0.0%wa, 0.0%hi, 0.3%si, 0.0%st Cpu2 : 17.6%us, 18.0%sy, 0.0%ni, 64.4%id, 0.0%wa, 0.0%hi, 0.0%si, 0.0%st Cpu3 : 10.2%us, 15.6%sy, 0.0%ni, 73.6%id, 0.3%wa, 0.0%hi, 0.3%si, 0.0%st Cpu4 : 12.5%us, 17.9%sy, 0.0%ni, 69.3%id, 0.0%wa, 0.0%hi, 0.3%si, 0.0%st Cpu5 : 12.7%us, 18.7%sy, 0.0%ni, 68.0%id, 0.3%wa, 0.0%hi, 0.3%si, 0.0%st Cpu6 : 13.6%us, 22.8%sy, 0.0%ni, 63.3%id, 0.3%wa, 0.0%hi, 0.0%si, 0.0%st Cpu7 : 10.1%us, 35.8%sy, 0.0%ni, 53.7%id, 0.3%wa, 0.0%hi, 0.0%si, 0.0%st Cpu8 : 10.3%us, 26.8%sy, 0.0%ni, 62.9%id, 0.0%wa, 0.0%hi, 0.0%si, 0.0%st Cpu9 : 14.6%us, 26.9%sy, 0.0%ni, 58.5%id, 0.0%wa, 0.0%hi, 0.0%si, 0.0%st Cpu10 : 7.5%us, 23.6%sy, 0.0%ni, 68.8%id, 0.0%wa, 0.0%hi, 0.0%si, 0.0%st Cpu11 : 16.7%us, 23.2%sy, 0.0%ni, 60.1%id, 0.0%wa, 0.0%hi, 0.0%si, 0.0%st Cpu12 : 9.1%us, 9.4%sy, 0.0%ni, 81.2%id, 0.3%wa, 0.0%hi, 0.0%si, 0.0%st Cpu13 : 7.7%us, 11.7%sy, 0.0%ni, 80.2%id, 0.3%wa, 0.0%hi, 0.0%si, 0.0%st Cpu14 : 6.4%us, 9.1%sy, 0.0%ni, 84.6%id, 0.0%wa, 0.0%hi, 0.0%si, 0.0%st Cpu15 : 13.0%us, 10.4%sy, 0.0%ni, 76.3%id, 0.3%wa, 0.0%hi, 0.0%si, 0.0%st Cpu16 : 9.8%us, 11.4%sy, 0.0%ni, 78.8%id, 0.0%wa, 0.0%hi, 0.0%si, 0.0%st Cpu17 : 7.4%us, 10.7%sy, 0.0%ni, 81.9%id, 0.0%wa, 0.0%hi, 0.0%si, 0.0%st Cpu18 : 10.1%us, 11.8%sy, 0.0%ni, 78.1%id, 0.0%wa, 0.0%hi, 0.0%si, 0.0%st Cpu19 : 11.7%us, 17.1%sy, 0.0%ni, 71.1%id, 0.0%wa, 0.0%hi, 0.0%si, 0.0%st Cpu20 : 3.4%us, 12.8%sy, 0.0%ni, 83.8%id, 0.0%wa, 0.0%hi, 0.0%si, 0.0%st Cpu21 : 7.0%us, 11.0%sy, 0.0%ni, 81.9%id, 0.0%wa, 0.0%hi, 0.0%si, 0.0%st Cpu22 : 15.4%us, 13.4%sy, 0.0%ni, 71.2%id, 0.0%wa, 0.0%hi, 0.0%si, 0.0%st Cpu23 : 10.4%us, 11.1%sy, 0.0%ni, 78.2%id, 0.3%wa, 0.0%hi, 0.0%si, 0.0%st Mem: 32862764k total, 22727968k used, 10134796k free, 151744k buffers Swap: 16777212k total, 34764k used, 16742448k free, 944064k cached
このように見た瞬間効果が分かる程度に、システムCPUも90%前後で頭打ちしてたものがコア平均15%
レベルまで改善。idleも0%
の全く空きなしの状態から、一気にコアによっては80%
程度まで空きができ改善していることが分かります。また、性能も概ね数倍以上になっていました。
perf
最後にperf
でこれまで支配的にシステムCPUを専有していたpte周りの処理を確認してみましょう。
Samples: 17M of event 'cycles', Event count (approx.): 268305691276 7.10% [kernel] [k] rwsem_spin_on_owner 6.47% [kernel] [k] anon_vma_interval_tree_insert 6.04% ld-2.12.so [.] do_lookup_x 5.60% [unknown] [.] 0x00007fbbab2c8bff 4.96% [kernel] [k] osq_lock 4.79% ld-2.12.so [.] strcmp 4.32% [kernel] [k] copy_page 1.85% ld-2.12.so [.] _dl_map_object 1.71% [kernel] [k] native_queued_spin_lock_slowpath 1.28% [kernel] [k] zap_pte_range 1.23% libc-2.12.so [.] _int_free 1.13% ld-2.12.so [.] _dl_name_match_p 1.13% ld-2.12.so [.] _dl_relocate_object 1.10% libc-2.12.so [.] _int_malloc 1.06% [kernel] [k] anon_vma_interval_tree_remove 0.99% [kernel] [k] clear_page_c_e 0.91% [kernel] [k] page_fault
このようにほぼpteに関する処理のオーバーヘッドがなくなったことがわかります。
まとめ
HugePages化することによって、24コアすべてのCPUが主にシステムCPUがによって専有されていた状況においても、idleをコアによっては80%近く空けることができるようになり、CPU使用率もほぼ100%から10%までシステムCPUを低減することができました。
これは、fork -> eveve suexec -> exeve php-cgi
における、pteのcopyやzapのループが単純計算で1/512の数に大幅低減されるから、と考えられます。
特に、前回着目したclone
だけでなくexecve
のzap_pte_range
などでも効いてくるので、結果的には非常にシステムCPUを節約できた事になります。
今後は、これによってCPUのidleが作れる様になった一方で、このようなfork元のプロセスが4GBを抱えるような状況で、CGIを実行する場合にそこからCPUをさらに効率よく使ってレスポンスを速く返すためにはどうしたらよいか、また、マルチプロセス対応の自作cgiデーモンによって起動時からプロセスの抱えるメモリサイズを減らしておいてfork(やvfork)する、といったアプローチについても調査しておりますのでご期待ください。