Linuxのパフォーマンスを改善する3つのTips
あらゆる最適化について言えることだが、何らかの簡単なベンチマークを行なわなければ、結果を本当に向上させることができたのかどうかを知ることはできない。Linux PC上では通常、数多くのプロセスが走っていて、それらが性能の測定に影響を与える可能性がある。その影響を最低限に抑えるために作業はランレベル1で行なうようにしよう。ランレベル1は、最低限のプロセスのみを実行するシングルユーザモードだ。ランレベル1で作業を行なうためには、ALT-F1を入力してコンソールに切り替え、ルートとしてログインして「init 1
」コマンドを実行する。その結果ほとんどのサービスとアプリケーションが終了するので、ばらつきのない結果を得ることができる。
しかしランレベル1においてもさらに、走っているべきでないプロセスがないかどうかを「ps xaf
」コマンドを使って確認しておいた方が良いだろう。私の場合、ddclient
プログラムが実行されていて(厳密に言うとスリープしていて)、最適化の結果に影響を与えかねなかったため、「killall ddclient
」を実行して終了した。
ハードディスクの速度を最適化する
最適化の最初のターゲットはハードディスクだ。「cat /etc/fstab
」コマンドとmount
コマンドを使用すれば、現在使用しているハードディスクを確認することができる。私の場合「cat /etc/fstab
」の実行結果は次のようになった。
/dev/hda3 / reiserfs acl,user_xattr,noatime 1 1 /dev/hda1 /boot ext2 acl,user_xattr 1 2 /dev/hda2 swap swap defaults 0 0 proc /proc proc defaults 0 0 sysfs /sys sysfs noauto 0 0 debugfs /sys/kernel/debug debugfs noauto 0 0 usbfs /proc/bus/usb usbfs noauto 0 0 devpts /dev/pts devpts mode=0620,gid=5 0 0 /dev/fd0 /media/floppy auto noauto,user,sync 0 0 /dev/hdd1 /media/disk2 reiserfs defaults,noatime 1 2
またmount
の実行結果は次の通りだった。
/dev/hda3 on / type reiserfs (rw,noatime,acl,user_xattr) proc on /proc type proc (rw) sysfs on /sys type sysfs (rw) debugfs on /sys/kernel/debug type debugfs (rw) udev on /dev type tmpfs (rw) devpts on /dev/pts type devpts (rw,mode=0620,gid=5) /dev/hda1 on /boot type ext2 (rw,acl,user_xattr) /dev/hdd1 on /media/disk2 type reiserfs (rw,noatime) securityfs on /sys/kernel/security type securityfs (rw)
上記から、私のメインのハードディスクは/dev/hdaであり、/dev/hda1、/dev/hda2、/dev/hda3という3つのパーティションがあることが分かる。またセカンダリ・ハードディスクは/dev/hddで、/dev/hdd1という1つのパーティションがあることが分かる。ここではメインのハードディスクを最適化する。
ハードディスクの設定の表示や変更は、 hdparm
コマンド(「hdparm」は「ハードディスク・パラメータ」の略)を使って行なうことができる。ただし変更すれば必ず良い結果がもたらされるというわけではなく、性能が低下したり、ひどく有害でデータが失われてしまう場合もある。「man hdparm
」を実行すればhdparm
コマンドの全オプションを見ることができて、危険なオプションについては「危険」と記されている。
それではまず、現時点での性能を見ることから始めよう。「hdparm -t /dev/hda
」コマンドを実行すれば、転送速度のテストが行なわれ、以下のような結果が表示される。
/dev/hda: Timing buffered disk reads: 10 MB in 3.14 seconds = 3.18 MB/sec
この結果から、ディスクが非常に低速であることが分かる。私は通常このコマンドを12回実行して、最小値と最大値を捨て、残りの値の平均を取るようにしている。これを行なうためには次のように実行すると良い。
for ((i=0;i<12;i++)) do; hdparm -t /dev/hda; done
上記のコマンドは、テストを12回実行する。最小値と最大値の排除や平均の計算もスクリプトで行なうようにしても良いが、それくらいは電卓でも十分簡単に済ませることができるだろう。
次に、「hdparm -v /dev/hda
」を実行してディスクのパラメータの現在の状態を見てみよう。
/dev/hda: multcount = 0 (off) IO_support = 0 (default 16-bit) unmaskirq = 0 (off) using_dma = 0 (off) keepsettings = 0 (off) readonly = 0 (off) readahead = 0 (off) geometry = 16383/255/63, sectors = 156301488, start = 0
一般的に最初に試す最適化は、転送の高速化のためにドライブが直接メモリにデータを保存できるようにするDMA(Direct Memory Access)を使用することだ。DMAを使用するようにするだけでも、かなりの高速化を実現することができる。私の場合、「hdparm -d1 /dev/hda
」を実行してハードディスクのDMAを有効にした後(なおオプションを-d0
とすればDMAは無効になり性能は落ちる)、再び速度を測ってみると16.25 MB/secに改善されていた。つまり元の速度から5倍も高速化したことになる!
DMA以外のオプションを試すこともできる。-c3
オプションを使用してhdparm -c3 /dev/hda
を実行すれば、IO_supportの値を変更することができる。私のシステムで試したところ16.4 MB/secという結果になった。これはわずかな改善だが、それでも設定しておく価値はある。
multcount
は、一度の操作で読み取ることのできるセクタ数を示すパラメータだ。「hdparm -i /dev/hda
」を実行すると、「maxMultSect=16」というやや紛らわしい表示も出るが、これはハードディスクが一度に読み取る数の最大値を示すものだ。したがって最大値に設定したい場合には「hdparm -m16 /dev/hda
」を実行すれば良い。
セクタの読み取りに関連するもう一つのパラメータに、readahead
がある。readahead
については、最適の結果を得るためには様々な値を試してみる必要がある。私の場合、最も良い結果を得ることができたのは「hdparm -a1024 /dev/hda
」という設定にしたときだった。このようにmultcount
とreadahead
を最適化した結果、ハードディスクの速度は33 MB/sec前後にまで向上した。この値を得るためには、readahead
の値を-a128
から-a256
、-a512
、-a1024
、-a2048
まで変えてみることで異なる組合わせを試してみたのだが、-a1024
の時が最も高速だった。ただしあなたも同じ結果になるとは限らない。当然ながらパラメータを変更する際には毎回上記の12回のテストを行なった。
またmultcount
の値についても-m1
から-m16
まで変えてみたところ、-m16
が最適だった。なお-m32
も試してみたが、私のハードディスクではこの値を扱うことができないと警告するエラーが返ってきた。
以上の変更を行なった結果、ハードディスクの速度を約11倍に上げることができた――なかなか悪くない結果だ。以上の他にも試すことのできるオプションがあと2、3個あるが、危険をともなう可能性がある。例えば「hdparm -u1 /dev/hda
」として割り込みに関していじることや、「hdparm -X
」コマンドを使って転送モードを変更することなどができる。今回これらについても試してみたが、特に高速化は見られなかったので元に戻しておくことにした。
「設定した値はhdparm -k1 /dev/hda
」を実行することにより保存することができるが、これは正しく最適化されていることが本当に確実になるまでは行なうべきではない。その代わりの方法として、起動時に実行するコマンドを記述する/etc/init.d/boot.localファイルの中にhdparmコマンドを追加しておくことができる。ただしこのファイルは少なくとも私が使っているopenSUSEシステムでは/etc/init.d/boot.localなのだが、ディストリビューションによっては異なるかもしれない。
この時点で、ハードディスクは最高速度で動いているはずだ。そこで次にやや高レベルに移動し、ファイルのアクセスを最適化しよう。
ファイルシステムのアクセスを最適化する
Linuxは、ファイルの作成時刻、最終更新時刻、最終アクセス時刻を記録する。しかし最終アクセス時刻の記録は、ファイルを読んだだけのときにも、システムが最終時刻の記録のためにファイルのディレクトリエントリを更新するため、通常はファイルのアクセス速度の低下を招くことになる。書き込みによって速度が低下することがあるということは、この更新を行なうのをやめてしまえば性能が向上するはずだ。
このスピードアップを実現するためには、ファイルシステムのマウント方法を変更すれば良い。そのためにはまず、ルートとして「cat /etc/fstab
」を実行してみよう。私の場合、以下のような結果になった。
/dev/hda3 / reiserfs acl,user_xattr 1 1 /dev/hda1 /boot ext2 acl,user_xattr 1 2 /dev/hda2 swap swap defaults 0 0 proc /proc proc defaults 0 0 sysfs /sys sysfs noauto 0 0 debugfs /sys/kernel/debug debugfs noauto 0 0 usbfs /proc/bus/usb usbfs noauto 0 0 devpts /dev/pts devpts mode=0620,gid=5 0 0 /dev/fd0 /media/floppy auto noauto,user,sync 0 0 /dev/hdd1 /media/disk2 reiserfs defaults 1 2
最適化するのに最も適したハードディスクのパーティションは、/
と/dev/hdd1だ。というのも/boot
は起動時にしか使用されず、/swap
は立ち入り禁止となっていて(Linuxカーネルが特別の目的に使用するため)、その他はハードディスクではないためだ。
変更するのは簡単だ。好きなテキストエディタで4列目のオプションに「,noatime
」を追加し、その後「mount -a
」コマンドを実行して全ファイルシステムを再マウントすれば良い。
性能はどうやってテストすれば良いだろうか。私の場合、まずはbonnie++パッケージを使ってみたが、bonnie++の行なうテストは特にファイルアクセスを重視しているわけではないため、結果は何とも判断しがたいものだった。
そこで代わりに再び自作のテストを行なうことにした。まず1,000個のファイルを作成しておき、その後、その中身を/dev/nullにコピーするのにかかる時間を測った。ファイルは以下のようにして作成した。
for ((i=0;i<1000;i++)) do ls >$i ; done
コピーにかかる時間は以下のようにして測った。
time cp * >/dev/null
そしてnoatime
オプションを付けた場合と付けなかった場合の両方で上記の2つを実行した。その結果、わずかだが性能の改善が見られた。ファイルアクセスの度に最終アクセス時刻を更新することがなくなったので、当然の結果だ。
この時点で、ハードディスクが可能な限り高速に動くようになったのに加え、ファイルアクセスも最適化することができた。そこで最後に、コマンドのロード時間を短縮するための最適化をしよう。
アプリケーションの読み込み時間を最適化する
LinuxのプログラムのほとんどはELF(Executable and Linkable Format)形式になっている。ELFでは必要な全ライブラリではなくそれらへのリファレンスだけが含まれていて、実行時のコードのロードの際に解決(リンク)するようになっているため、通常はファイルのサイズが小さくなっている。
これは古典的な時間/空間トレードオフの問題であり、ELFの場合、プログラムファイルの大きさは小さいが、ロードのために長い時間が必要となっている。同じライブラリを使用するプログラムは数多くあるので、ライブラリを共有することによってメモリ空間も節約することができる。また、小さいプログラムはたいていライブラリをほとんど必要としないので、リンクは通常は短時間で済む。しかし多数のライブラリを使用する大規模なプログラムでは、リンクにかなりの時間がかかることもある。
そこでもう少しディスク空間を使用しても良いという場合には、prelinkコマンドを使用することにより、リンク作業を前もって行なってプログラムのファイル内にライブラリを一緒に保存しておき、読み込みが終わればすぐに実行可能になるようにしておくことができる。(厳密に言うとこれは正しくなく、実際にはプログラムが読み込まれる際に、プログラムをプレリンクして以来ライブラリが変更されていないかどうかライブラリの確認が行なわれる。しかしこの確認は瞬時に終わる)。
ただし、プレリンクし始める前に、まずは/etc/prelink.confという設定ファイルにいくつか重要な項目を設定する必要がある。このファイルは、共有ライブラリがある場所をprelinkに伝える。また、プレリンク可能なプログラムすべてをプレリンクする-a
オプションを使用する場合に、プレリンクするべきプログラムがある場所をprelinkに伝える。
/etc/prelink.confの書き方は単純で、コメント(# から始まる行)と空行の他に、以下のような行を追加することができる。
-l someDirectory -h someOtherDirectory -b someFilePatternToAvoid
-l
の行では、処理するディレクトリを指定する。また-h
の行は-l
の行と同じだが、ただしシンボリックリンクの追跡も有効にする。-b
の行は、処理するべきでない「ブラックリスト」のプログラムを示す(パターンで指定することもできる)。プレリンクすることのできないプログラムが前もって分かっている場合には、-b
の行を適切に追加しておくことで、実行時にエラーメッセージが出ないようにすることができる。
一つのプログラムをプレリンクするには、「prelink プログラム名
」とするだけだ。プログラムをプレリンクすることが可能な場合は(不可能な場合もある)、元のバイナリファイルがより大きな新しい版で上書きされる。また「prelink -a
」を実行することで、プレリンク可能なもののすべてをプレリンクすることもできる。この場合、設定ファイルの中の-l
と-h
で指定されたすべてのディレクトリを調べて、見つかったすべてのプログラムをプレリンクする。なお-n
オプションをさらに追加して実行すれば「予行演習」をすることもできる。この場合すべての変更の表示だけを行ない実際には変更しないので、実際に実行したときに行なわれるはずのことを前もって確認することができる。
警告:システム上のライブラリの数が多く、メモリの量が少ないときには、prelinkがメモリ量を節約するために-m
オプションを追加する必要があるだろう。
プレリンクしたファイルが気に入らない(あるいはライブラリが新しくなる度に毎回更新するのが面倒になった)ときには、-u
オプションを使用してプレリンクしていない元の状態に戻すことができる。したがって「prelink -u プレリンクされているプログラム
」を実行すれば、プレリンクされているプログラムを以前の形式に問題なく戻すことができる。
プレリンクしたプログラムは他のプログラムとまったく同じように実行することができるが、通常はロードの時間が短縮され、デスクトップの反応速度の向上を体感することができる。結果については異なる見方があるが、私が調べたほとんどの文書では明らかな高速化があると示されていた。今回は以下のような簡単なコマンドで時間を測定してみた。
prelink diff time diff prelink -u diff time diff
結果は期待の持てるものだった(人間が関わる時間が含まれてしまうのを避けるために、ロードが終わった後すぐに終了するプログラムを試した)。もちろん今回行なったのは非常に小さな例に過ぎないが、一般的な結果も同様のものになるはずだ。
まとめ
性能の向上は、あらゆる場合において適切で現実的な目標だ。この記事で紹介した3つの案は、それぞれ独立しているが関連した方法であり、Linux PCの全体的な速度を向上させることができる。手元のハードウェアからさらにもう少し魅力を引き出すことができるのは、やはり嬉しいことだ。
Federico Kerekiはウルグアイのシステムエンジニア。20年に渡るシステム開発、コンサルティング、大学講師の経験がある。