パフォーマンスボトルネックの探し方(続き) : やっぱり Sun がスキ! やっぱり Sun がスキ!

やっぱり Sun がスキ!

http://blogs.sun.com/yappri/date/20090223 2009年 2月 23日 月曜日

パフォーマンスボトルネックの探し方(続き)

はじめに

前回の エントリ では nxge のチューニングを施して、スループットが 3.6Gbps になった状態での解析結果までをご紹介しました。今回はその続きです。

Apache の最新版を使ってみる

これまで使用して来たバンドル版の Apache はバージョン 2.0.63 です。一方、最新リリースはバージョン 2.2.11 です。2.2.x では I/O の実装が変更されており、これによって性能が上がる可能性があります。Apache の最新版を使用して、どの程度性能が出るか試してみたいと思います。Apache のコンパイルは以下の手順で行いました。今回は 64bit でコンパイルしてあります。また、Apache のキャッシュ機能は使用していません。

 # LDFLAGS="-B direct" CFLAGS='-m64' ./configure --prefix=/usr/local/apache2.64bit
 # gmake
 # gmake install
 # PATH=/usr/local/apache2.64bit/bin:${PATH}; export PATH
 # LD_LIBRARY_PATH_64=/usr/sfw/lib/amd64; export LD_LIBRARY_PATH_64
 # vi /usr/local/apache2.64bit/conf/httpd.conf
 # /usr/local/apache2.64bit/bin/apachectl start
 Apache 2.2 系を使ってみる事にします

早速性能を測定

dladm コマンドでトラフィックを見てみると、534MB/s, つまり 4.3Gbps ほどの性能が出ています。先ほどは 3.6Gbps でしたので 700Mbps ほど性能が向上しました。初めて 4Gbps を越えましたが、このくらい出ていればリンクアグリゲーションでギガビットイーサネットのポートを束ねるよりも効果的であると言えると思います。まだ 10Gbps の半分も出ていませんので、もう少し解析を続けたいと思います。

 # dladm show-dev -s -i 1 nxge0
 ...
                 ipackets  rbytes         ierrors opackets        obytes      oerrors
 nxge0           125715    7550295     0       396034    568337848   0       
                 ipackets  rbytes         ierrors opackets        obytes      oerrors
 nxge0           125957    7567101     0       390700    560692368   0       
                 ipackets  rbytes         ierrors opackets        obytes      oerrors
 nxge0           126931    7621305     0       392393    563126976   0       
                 ipackets  rbytes         ierrors opackets        obytes      oerrors
 nxge0           126817    7614564     0       392250    562935589   0       
                 ipackets  rbytes         ierrors opackets        obytes      oerrors
 nxge0           127240    7638855     0       389528    559050398   0       
                 ipackets  rbytes         ierrors opackets        obytes      oerrors
 nxge0           127175    7635648     0       391883    562428220   0       
 性能が 4Gbps を越えました

vmstat

まずは vmstat を見てみます。cpu の id が 0% になっており、やはり CPU ボトルネックです。cpu の sy が 100% なので、CPU は sys で使われている様です。

 # vmstat 1
  kthr      memory            page            disk          faults      cpu
  r b w   swap  free  re  mf pi po fr de sr s0 s1 s2 --   in   sy   cs us sy id
 ...
  18 0 0 28253364 30362228 75904 11 0 0 0 0 0 0 0  0  0 595386 1150 128655 0 100 0
  17 0 0 28253364 30362148 78020 11 0 0 0 0 0 0 0  0  0 611144 1109 127456 0 100 0
  16 0 0 28253364 30362240 78773 11 0 0 0 0 0 0 0  0  0 616220 1070 127647 0 100 0
  19 0 0 28253364 30362072 77473 11 0 0 0 0 0 0 0  0  0 607398 1036 127760 0 100 0
  22 0 0 28253364 30362200 75965 11 0 0 0 0 0 0 0  0  0 596055 1269 128268 0 100 0
  23 0 0 28253364 30362080 74654 11 0 0 0 0 0 0 0  0  0 588108 1122 129089 0 100 0
 CPU ボトルネックです

prstat

続いて prstat も見てみます。httpd の SYS が多いので、先ほどと同様 Apache のプロセスが SYS の発生源になっています。

 # prstat -mL 1
 ...
    PID USERNAME USR SYS TRP TFL DFL LCK SLP LAT VCX ICX SCL SIG PROCESS/LWPID 
  20798 daemon   0.0  11 0.0 0.0 0.0 0.0  72  17  1K   3   9   0 httpd/1
  20687 daemon   0.0  10 0.0 0.0 0.0 0.0  65  25  1K   1   0   0 httpd/1
  20857 daemon   0.0 9.8 0.0 0.0 0.0 0.0  72  19  1K   4  11   0 httpd/1
  20686 daemon   0.0 9.3 0.0 0.0 0.0 0.0  76  15  1K   7  15   0 httpd/1
  20801 daemon   0.0 7.3 0.0 0.0 0.0 0.0  82  11 864   9   7   0 httpd/1
  20877 daemon   0.0 7.0 0.0 0.0 0.0 0.0  81  12 943   2   6   0 httpd/1
  20743 daemon   0.0 7.0 0.0 0.0 0.0 0.0  83  10  1K   3   2   0 httpd/1
  20837 daemon   0.0 7.0 0.0 0.0 0.0 0.0  81  13 942   1   0   0 httpd/1
  20659 daemon   0.0 6.6 0.0 0.0 0.0 0.0  75  19 842   2   7   0 httpd/1
  20731 daemon   0.0 6.4 0.0 0.0 0.0 0.0  78  15 802   8  11   0 httpd/1
  20835 daemon   0.0 6.0 0.0 0.0 0.0 0.0  84  10 722   0   0   0 httpd/1
 CPU を消費しているのは Apache が発行しているシステムコールでした

システムコールの種類

truss -c でシステムコールの種類を割り出します。出力の中の elapsed の値は計測を行っていた期間を示しており、sys totals の値はシステムコールの総計です。詳しく見てみると sys totals が約 67 秒なのに対し、sendfilev というシステムコールの実行時間もほぼ同じ 67 秒となっています。どうやら sendfilev が殆どの時間を使っている様に見て取れます。sendfilev は sendfile のバリエーションですが sendfile はデータの受け渡しがカーネル内で済むので、read してから write する場合の様にユーザランドを経由する場合に比べて効率的です。これが 2.0 に比べて性能が向上した理由の一つであると考えられます。

 # truss -c -p `pgrep -nx httpd`
 ^C
 syscall               seconds   calls  errors
 read                     .015     631      12
 write                    .031     613
 open                     .024     613
 close                    .009     619
 stat                     .043     613
 getpid                   .004     613
 fcntl                    .000       6
 fcntl                    .000       6
 sendfilev              67.145    1164     551
 pollsys                  .015     563
 accept                   .000       6
 shutdown                 .000       6
 getsockname              .000       6
                      --------  ------   ----
 sys totals:            67.291    5459    563
 usr time:                .230
 elapsed:             3130.450
 CPU を消費しているシステムコールは sendfilev でした

何が行われているかをもう少し細かく見てみる

truss を使って更に詳しく見てみましょう。まず read システムコールが発行されています。read されている中身は "GET /008 HTT..." ですので、HTTP の GET リクエストを受け取っているのだと思われます。read の引数にあるファイルディスクリプタ 11 番は恐らくソケットでしょう。次に write をしています。write の中身は "192.168.0.2 " ですが、これは Apache のアクセスログの書き込みであると思われます。続いて、リクエスト要求のあったファイルを stat で確認しています。次のシステムコールは getpid で、プロセス ID を取得しています。open でリクエストのあったファイルを開き、最後に sendfilev で送信しています。

 # truss -o /var/tmp/truss-httpd-02.dump -E -vall -p `pgrep -nx httpd`
 ...
  0.0000 read(11, " G E T   / 0 0 8   H T T".., 8000)    = 105
  0.0001 write(8, " 1 9 2 . 1 6 8 . 0 . 2  ".., 78)      = 78
  0.0000 close(12)                                       = 0
  0.0001 stat("/data/web/008", 0xFFFFFD7FFFDFF3A0)       = 0
     d=0x000000B500010002 i=12    m=0100766 l=1  u=0     g=0     sz=10485760
         at = Feb  2 20:24:19 JST 2009  [ 1233573859 ]
         mt = Jan 30 23:06:43 JST 2009  [ 1233324403 ]
         ct = Feb  2 13:39:24 JST 2009  [ 1233549564 ]
     bsz=131072 blks=20485 fs=zfs
  0.0000 getpid()                                        = 20928 [20649]
  0.0000 open("/data/web/008", O_RDONLY)                 = 12
  0.1153 sendfilev(0, 11, 0x0058B1B8, 2, 0xFFFFFD7FFFDFEDB0) = 10486053
 sfv_fd=-2       sfv_flag=0x0    sfv_off=5833320 sfv_len=293
 sfv_fd=12       sfv_flag=0x0    sfv_off=0       sfv_len=10485760
 read -> write -> stat -> open -> sendfilev の順に処理が流れていました

その他の分析

システムコールの流れは分かりましたが、もう少し別の角度からも状況を分析したいと思います。使用するコマンドは mpstat です。出力を見てみると Apache 2.0 の時に比べて xcal, intr, migr, smtx が目立って多くなっています。いずれも CPU の消費を増やす原因となります。migr はプロセスが別のプロセッサに移動した回数を示しており、以前はこの値が 200 を越えると多いと言われていました。

 # mpstat 1
 ...
 CPU minf mjf xcal  intr ithr  csw icsw migr smtx  srw syscl  usr sys  wt idl
   0    0   0 60131 77736 4187 14579   43 1300 1730 7157    25    0 100   0   0
   1    0   0 62342 77830 4036 14572   37 1254 1742 7189    19    0 100   0   0
   2    0   0 68504 75532 3900 15175   82 1164 1791 7842    89    1  99   0   0
   3    0   0 70467 75267 3829 15700   88 1254 1709 7943   343    1  99   0   0
   4    0   0 70624 76365 3808 15938   95 1305 1844 8298   101    0 100   0   0
   5    0   0 66244 76809 3622 16717   92 1341 1862 8094   108    0 100   0   0
   6    0   0 70596 76814 3992 16525   89 1356 1879 8080   115    1  99   0   0
   7   11   0 78071 74775 3712 17020   88 1143 1970 8680   341    1  99   0   0
 xcal, intr, migr, smtx が多くなっています

インタラプトの原因

mpstat で intr が多かった原因を探るため、lockstat コマンドを使用します。lockstat に -I オプションを付けるとカーネル内のインタラプト処理のプロファイルを見る事が出来ます。-g オプションを付けると、関数 a() が別の関数 b() を呼び出した時に、b() の行った処理も a() に加算される様になり、インタラプトを発生させている大元の処理を統計情報上に浮かび上がらせる事が可能です。出力結果は Caller が呼び出し元の関数で、Count が実行回数を示しています。出力内容を見てみると、sys_syscall と sendfilev がトップに表示されていますので、intr の原因は sendfilev の様です。

 # lockstat -kgIW sleep 10
 
 Profiling interrupt: 7848 events in 10.109 seconds (776 events/sec)
 
 Count genr cuml rcnt     nsec Hottest CPU+PIL        Caller                  
 -------------------------------------------------------------------------------
  5161  66% ---- 0.00     7356 cpu[2]                 sys_syscall             
  5152  66% ---- 0.00     7302 cpu[2]                 sendfilev               
  5151  66% ---- 0.00     7303 cpu[2]                 sendvec_chunk           
  5115  65% ---- 0.00     7308 cpu[2]                 snf_segmap              
  2977  38% ---- 0.00     7055 cpu[2]                 tcp_output              
  2970  38% ---- 0.00     7059 cpu[2]                 tcp_wput_data           
  2891  37% ---- 0.00     6941 cpu[2]                 kstrwritemp             
  2882  37% ---- 0.00     7091 cpu[2]                 tcp_send                
  2837  36% ---- 0.00     6960 cpu[2]                 sostream_direct         
  2803  36% ---- 0.00     6969 cpu[2]                 tcp_wput                
 sendfilev がインタラプトを発生させている原因でした

lockstat 続き

lockstat -I の -g オプションを外した出力も合わせて掲載しておきます。bcopy と mutex_enter が頻繁に呼び出されている事が分かります。もしあなたがカーネル開発者であれば、この情報がチューニングに役立つはずです。

 # lockstat -kIW sleep 10 
 
 Profiling interrupt: 7848 events in 10.106 seconds (777 events/sec)
 
 Count indv cuml rcnt     nsec Hottest CPU+PIL        Caller                  
 -------------------------------------------------------------------------------
  1196  15%  15% 0.00     5029 cpu[5]                 bcopy                   
  1044  13%  29% 0.00     4835 cpu[2]                 mutex_enter             
   541   7%  35% 0.00     8162 cpu[6]                 mutex_vector_exit       
   340   4%  40% 0.00     4763 cpu[5]                 nxge_start              
   294   4%  44% 0.00     4815 cpu[0]                 atomic_add_int_nv       
   199   3%  46% 0.00     4772 cpu[2]                 atomic_add_32           
   197   3%  49% 0.00     4898 cpu[7]                 tcp_send_data           
 lockstat で -g オプションを付けないと枝葉の関数を見る事が出来ます

スピンミューテックスの原因

smtx はロックが取得出来なかった場合にスピンした回数です。頻繁にスピンが発生すると CPU を空費してしまうため、なるべく避けたい状態です。スピンミューテックスも lockstat コマンドで詳しく見る事が可能です。こちらも上位に sys_syscall と sendfilev が上がっており、sendfilev がスピンミューテックスの原因になっている事が分かります。

 # lockstat -g -D 10 sleep 10
 
 Adaptive mutex spin: 768605 events in 10.187 seconds (75452 events/sec)
 
 Count genr cuml rcnt     nsec Lock                   Caller                  
 -------------------------------------------------------------------------------
 701380  91% ---- 0.00    29158 0xffffffff86fc8f58     sys_syscall+0x17b       
 698865  91% ---- 0.00    29156 0xffffffff86fc8f58     sendfilev+0x380         
 698865  91% ---- 0.00    29156 0xffffffff86fc8f58     sendvec_chunk+0x85c     
 365684  48% ---- 0.00    28361 0xffffffff86fc8f58     rrw_enter+0x18          
 365684  48% ---- 0.00    28361 0xffffffff86fc8f58     rrw_enter_read+0x11     
 364292  47% ---- 0.00    29465 0xffffffff86fc8f58     fop_getpage+0x47        
 364292  47% ---- 0.00    29465 0xffffffff86fc8f58     segmap_fault+0x118      
 364292  47% ---- 0.00    29465 0xffffffff86fc8f58     zfs_shim_getpage+0x37   
 364292  47% ---- 0.00    29465 0xffffffff86fc8f58     snf_segmap+0x214        
 335696  44% ---- 0.00    30026 0xffffffff86fc8f58     rrw_exit+0x11           
 -------------------------------------------------------------------------------
 ...
 smtx の原因も sendfilev でした

ここまでのまとめとチューニングの候補

Apache 2.0.x から 2.2.x に変更する事で性能が上がりました。性能が向上した原因はシステムコールが read, write から sendfilev に変更された事の様です。一方、sendfilev を使用する事によりインタラプトやスピンミューテックスが増加しました。

先ほどは Apache のチューニングが有効であると書きましたが、今度は一つのシステムコールの中で CPU を使っているので、アプリケーションのチューニングは効果が無さそうです。一方 sendfilev() はファイルシステムからデータを読み出していますので、ファイルシステムのチューニングは有効であると思われます。ファイルシステムに Sun StorageTek QFS を使用してブロックサイズを増やしてあげると非常に効果が高そうです。

更に、CPU 性能がボトルネックで、プログラムの並列度には制限が無いため Sun SPARC Enterprise T5440 に置き換えると非常に効果的かもしれません。

lighttpd(ライティ)

今回のベンチマークは HTTP のダウンロードの性能を計測していますが、必ず Apache を使用しなくてはいけない訳ではありません。そこで Youtube や Wikipedia のコンテンツ配信でも使用されているという lighttpd を試してみる事にします。

lighttpd は軽量・高速が売りの http サーバです。元々はシングルプロセス、シングルスレッドの実装でしたが、現在はマルチプロセスで構成する事が可能になっており、今回の様なマルチコアのシステムでもリソースを有効に使えます。また、Solaris 向けのチューニングも幾つか入っており、性能が期待出来ます。

 Youtube に倣って lighttpd を使用してみます

インストール

lighttpd は最新版の 1.4.20 をダウンロードして、64bit でコンパイルしてあります。

 # /usr/local/sbin/lighttpd -v
 lighttpd-1.4.20 - a light and fast webserver
 Build-Date: Feb  2 2009 11:51:34
 # file /usr/local/sbin/lighttpd 
 /usr/local/sbin/lighttpd:     ELF 64-bit LSB executable AMD64 Version 1, dynamically linked, not  stripped, no debugging information available
 lighttpd は最新版を 64bit でコンパイルしました

lighttpd の構成

lighttpd の構成は lighttpd.conf に記述します。server.event-handler パラメータは多重 I/O 待ちの方法を設定するパラメータです。今回は Solaris 固有の /dev/poll を使用する事にしました。server.network-backend はソケットにデータを書き出す方法を設定するパラメータです。writev を使用する様に設定してあります。server.max-worker はフォークするプロセスの数です。今回は 8 コアのサーバなので 8 を指定しました。

 # cat /etc/lighttpd/lighttpd.conf 
 server.document-root = "/data/web/"
 server.errorlog             = "/var/log/lighttpd/error.log"
 server.port = 80
 mimetype.assign = (
   ".html" => "text/html", 
   ".txt" => "text/plain",
   ".jpg" => "image/jpeg",
   ".png" => "image/png" 
 )
 static-file.exclude-extensions = ( ".fcgi", ".php", ".rb", "~", ".inc" )
 index-file.names = ( "index.html" )
 server.event-handler="solaris-devpoll"
 server.network-backend = "writev"
 server.max-worker = 8
 Solaris の /dev/poll を使用する設定にしてあります

早速測定

dladm コマンドで性能を測定します。約 803MB/s なので 6.4Gbps ほど出ている様です。先ほどが 4.3Gbps でしたので、2Gbps 以上速くなりました。1.5 倍以上性能が向上したことになります。何故これほど性能が上がったのか原因を探って行きたいと思います。

 # dladm show-dev -s -i 1 nxge0
 ...
                 ipackets  rbytes         ierrors opackets        obytes      oerrors
 nxge0           121333    7292772     0       551333    841121608   0       
                 ipackets  rbytes         ierrors opackets        obytes      oerrors
 nxge0           120944    7268148     0       551986    842231276   0       
                 ipackets  rbytes         ierrors opackets        obytes      oerrors
 nxge0           120353    7229502     0       550841    840587112   0       
                 ipackets  rbytes         ierrors opackets        obytes      oerrors
 nxge0           120500    7235352     0       550710    840466908   0       
                 ipackets  rbytes         ierrors opackets        obytes      oerrors
 nxge0           120853    7255338     0       551833    842210781   0       
                 ipackets  rbytes         ierrors opackets        obytes      oerrors
 nxge0           120397    7228176     0       550543    840241368   0       
 lighttpd で性能が 1.5 倍になりました!

システムの状況

vmstat でシステム全体の状況を見てみますと、id が 0 なのでやはり CPU ボトルネックです。システムコールの回数と sys の割合は先ほど同様高くなっています。先ほどに比べて re と mf の数値が高くなっています。mf はマイナーフォルトでメモリのページングが多く発生している兆候となります。ページングも CPU を消費する処理です。

 # vmstat 1
 ...
  kthr      memory            page            disk          faults      cpu
  r b w   swap  free  re  mf pi po fr de sr s0 s1 s2 --   in   sy   cs us sy id
  14 0 0 28594444 30200584 60604 78476 0 0 0 0 0 0 0 0 0 40506 32755 50390 1 99 0
  14 0 0 28594444 30210288 57141 75336 0 0 0 0 0 0 0 0 0 40588 32561 50220 1 99 0
  11 0 0 28594444 30210140 58610 76582 0 0 0 0 0 0 0 0 0 40551 33251 49931 1 99 0
  14 0 0 28594444 30204448 60581 78293 0 0 0 0 0 0 0 0 0 40741 33673 50099 1 99 0
  18 0 0 28594444 30199220 63630 80969 0 0 0 0 0 0 0 0 0 40588 33167 49921 1 99 0
  17 0 0 28594444 30172716 66668 83617 0 0 0 0 0 0 0 0 0 40442 32964 49719 1 99 0
  6 0 0 28594444 30170932 70143 86752 0 0 0 0 0 0 0 0 0 40295 32065 49771 1 99 0
 ページングが増えている様です

prstat

続いて prstat -L でプロセスの稼働状況を確認します。prstat は他の UNIX でいう top コマンドと同等のコマンドです。出力内容を見ると、シングルスレッドの lighttpd プロセスが 8 つ起動している様です。RSS のカラムがプロセスサイズですが lighttpd プロセスのサイズは 50MB から 100MB ほどあり、多少大きい様に思えます。

    PID USERNAME  SIZE   RSS STATE  PRI NICE      TIME  CPU PROCESS/LWPID      
  21130 root      159M  101M cpu6    54    0   0:04:22 7.8% lighttpd/1
  21131 root      153M   93M cpu2    58    0   0:04:19 7.8% lighttpd/1
  21127 root       78M   50M cpu0    54    0   0:04:22 7.7% lighttpd/1
  21129 root      100M   64M run     55    0   0:04:20 7.7% lighttpd/1
  21126 root       75M   47M cpu7    57    0   0:04:21 7.7% lighttpd/1
  21128 root       87M   53M cpu5    60    0   0:04:18 7.6% lighttpd/1
  21125 root       91M   56M run     55    0   0:04:20 7.6% lighttpd/1
  21132 root       94M   52M cpu4    53    0   0:04:18 7.6% lighttpd/1
  21136 root     3892K 3080K cpu0    59    0   0:00:01 0.1% prstat/1
   1009 root     3108K 1964K sleep   59    0   0:00:44 0.0% iostat/1
 lighttpd のプロセスサイズは 50MB - 100MB になっていました

mpstat

mpstat も見てみましょう。Apache 2.2 の時に出ていた xcal が無くなりました。migr も大分少なくなっています。

 CPU minf mjf xcal  intr ithr  csw icsw migr smtx  srw syscl  usr sys  wt idl
   0 11348   0    0  5224 3366 6132 1697  181 1644   57  4184    1  99   0   0
   1 11974   0    0  4836 3197 6034 1669  187 1745   54  3456    1  99   0   0
   2 13513   0    0  4937 3220 6307 1801  175 1879   81  4409    2  98   0   0
   3 12179   0    0  5037 3310 6147 1785  188 1763   66  4106    1  99   0   0
   4 11807   0    0  4910 3150 6412 1704  206 1755   43  4017    1  99   0   0
   5 12650   0    0  4928 3191 6494 1768  222 1848   60  4198    1  99   0   0
   6 12438   0    0  5036 3318 6125 1724  217 1832   41  4015    1  99   0   0
   7 11436   0    0  4972 3321 6025 1691  163 1807   47  3821    1  99   0   0
 xcal が無くなりました

システムコールを観察する

再び truss -c の結果です。write が突出している一方、read が殆どありません。mmap / munmap の回数が多い様です。先ほど prstat でプロセスサイズが大きかったのはファイルを mmap してから write している所為かもしれません。

 # truss -c -p `pgrep -nx lighttpd`
 ^C
 syscall               seconds   calls  errors
 read                     .004     408       4
 write                  22.667  120902
 open                     .088    4586
 close                    .024    4574
 time                     .000     299
 stat                     .120    4182
 ioctl                    .143     707
 fcntl                    .001     418
 fcntl                    .000      14
 mmap                     .158    7586
 munmap                  2.898    7573
 writev                   .009     404
 memcntl                  .281    7586
 accept                   .001      28      14
                      --------  ------   ----
 sys totals:            26.399  159267     18
 usr time:                .612
 elapsed:               49.520
 lighttpd は mmap, munmap している様です

システムコールの流れ

truss コマンドを使って実際に何が起きているのかを見てみましょう。ログを見ると、まず HTTP の GET を read() で受け取り、リクエストされたファイルを open() して、mmap() し、それをそのまま write() している様です。また一回の write() のサイズは 64KB である事が分かります。

 # truss -o /var/tmp/truss-lighttpd-02.dump -E -vall -p `pgrep -nx lighttpd`
 # less /var/tmp/truss-lighttpd-02.dump
 ...
  0.0000 read(211, " G E T   / 0 3 3   H T T".., 127)    = 81
  0.0001 writev(211, 0x00449CF0, 1)                      = 188
         iov_base = 0x00749D50  iov_len = 188
  0.0000 open("/data/web/033", O_RDONLY)                 = 212
  0.0000 fcntl(212, F_SETFD, 0x00000001)                 = 0
  0.0000 mmap(0x00000000, 524288, PROT_READ, MAP_SHARED, 212, 0) = 0xFFFFFD7FF7A13000
  0.0001 memcntl(0xFFFFFD7FF7A13000, 524288, MC_ADVISE, MADV_WILLNEED, 0, 0) = 0
  0.0002 write(211, "¥0¥0¥0¥0¥0¥0¥0¥0¥0¥0¥0¥0".., 524288) = 65536
  0.0000 write(5, "D3¥0¥0¥004¥0¥0¥0", 8)                 = 8
  0.0000 accept(3, 0xFFFFFD7FFFDFF810, 0xFFFFFD7FFFDFF80C, SOV_DEFAULT) Err#11 EAGAIN
  0.0005 write(6, "¥0¥0¥0¥0¥0¥0¥0¥0¥0¥0¥0¥0".., 458752)  = 65536
  0.0005 write(7, "¥0¥0¥0¥0¥0¥0¥0¥0¥0¥0¥0¥0".., 327680)  = 65536
  0.0004 write(8, "¥0¥0¥0¥0¥0¥0¥0¥0¥0¥0¥0¥0".., 65536)   = 65536
  0.0003 write(10, "¥0¥0¥0¥0¥0¥0¥0¥0¥0¥0¥0¥0".., 262144) = 65536
  0.0003 write(12, "¥0¥0¥0¥0¥0¥0¥0¥0¥0¥0¥0¥0".., 393216) = 65536
  0.0003 write(13, "¥0¥0¥0¥0¥0¥0¥0¥0¥0¥0¥0¥0".., 131072) = 65536
  0.0009 write(14, "¥0¥0¥0¥0¥0¥0¥0¥0¥0¥0¥0¥0".., 458752) = 65536
 mmap したファイルを write している様です

Software Large Send Offload

Solaris の LSO 機能を利用すると CPU 負荷を減らす事が出来ます。nxge の場合は nxge.conf で soft-lso-enable というパラメータを設定します。

 # vi /platform/i86pc/kernel/drv/nxge.conf 
 soft-lso-enable = 1;
 # reboot

LSO を使用した場合の測定結果

dladm の出力は 846MB/s ほどですので大体 6.7Gbps の性能になりました。この記事の一番最初では 1.9Gbps でしたので、スループットは 3.5 倍以上になりました。7Gbps 近くまで出ていますので、10GbE のメリットを十分享受出来ていると言っていいのではないかと思います。

 # dladm show-dev -s -i 1 nxge0
 ...
                 ipackets  rbytes         ierrors opackets        obytes      oerrors
 nxge0           120475    7234656     0       583893    890204366   0       
                 ipackets  rbytes         ierrors opackets        obytes      oerrors
 nxge0           120443    7232979     0       582062    887277854   0       
                 ipackets  rbytes         ierrors opackets        obytes      oerrors
 nxge0           120462    7232418     0       583000    889047252   0       
                 ipackets  rbytes         ierrors opackets        obytes      oerrors
 nxge0           120278    7222350     0       582143    887593626   0       
                 ipackets  rbytes         ierrors opackets        obytes      oerrors
 nxge0           120124    7214892     0       581692    886646548   0       
                 ipackets  rbytes         ierrors opackets        obytes      oerrors
 nxge0           119646    7188804     0       578414    881221640   0       
 7Gbps 近くまで性能が上がりました!

ここまでのまとめと考えられるチューニング

Apache 2.2.x から lighttpd に変更する事で大幅に性能が上がりました。性能が上がった要因はシステムコールが sendfilev から mmap + write に変更された為の様です。また write のサイズも Apache 2.0.x に比べて大きくなっていました。更に LSO を使用する事で 7Gbps 近くまで性能を挙げる事が出来ました。

更に性能を上げる為に、Jumbo Frame を使用してパケットサイズを 8K 等にすれば効果があると思われます。また mmap による page-in が多少足かせになっている様ですので、Solaris の MPSS(multiple pagesize support) 機能を使用してプロセスのメモリページサイズを大きく取ってあげれば I/O の抜けが良くなりそうです。

今回は Apache 2.0.x, 2.2.x, lighttpd を使用しましたが、Open Web Server や nginx 等、別の HTTP サーバも試してみると良いかもしれません。もちろんサーバをもっと性能が良い Sun SPARC Enterprise T5440 等に変更すれば更にスループットは向上します。

全体のまとめ

最後に簡単に振り返ってみますと、HTTP サーバには幾つかの実装があり、それぞれ性能や性質が異なっていました。Apache 2.0 は 8KB 単位で read して write しており、Apache 2.2 は sendfilev を使用していました。lighttpd は mmap して 64KB 単位で write していました。結果として一番速かったのは lighttpd でしたが、Solaris の解析ツールを使用する事で何が性能に寄与しているのかをより深く知る事が出来ました。vmstat を出発点として色々なツールを組み合わせる事により、アプリケーションの動きをつぶさに見て取れる事がご理解頂けたと思います。

一点強調しておきたいのは今回の解析例では DTrace を使用していないという事です。もちろん DTrace を使えば更に深く広く調査を進める事が可能ですが、DTrace のスクリプトを作成せずとも今回ご紹介した様な解析が出来る様に沢山のツールが揃っている事が Solaris の優れた点の一つだと思います。今回ご紹介出来なかった中にも便利なツールが沢山ありますので、是非色々と試して頂けたらと思います。以上、Solaris でボトルネック解析をする一例をご紹介しました。

注意

今回は検証用マシン調達の都合上、数年前の古い CPU を使用しています。最新の機材を使用すれば 10GbE を溢れさせる事は十分可能です。一例として http://blogs.sun.com/puresee/entry/10_gigabit_ethernet_on_ultrasparc1 をご覧下さい。

ダウンロードするファイルのサイズや数を変更した場合、今回の解析結果はそのまま当てはまらないかもしれません。lighttpd の実装は mmap を使用しているとご紹介しました。例えば、今回の負荷シナリオではコンテンツのサイズを 10MB としていましたが、これが 1GB だったら性能はどうなるでしょうか。

今回は負荷生成に ab を使用しましたが、より現実的な負荷シナリオやレスポンスタイムの監視を実現するため JMeter 等のツールを使われるとより良いベンチマークになると思います。

参考文献

投稿されたコメント:

コメント
  • HTML文法 不許可