ports: 高速化対策2006年04月26日 08時59分08秒

前回の分析自体は捨てたものでもなかったが、一般的な対策を取ったため、折角取り上げた意味が少なかった。しかし、今回は ports に最重点を置き、高速化を計るので複数で共有して利用している機械には向かない。

それでは、ports の更新の準備を始めるとする。

やはり、最初にやるべき事は、一番 CPU が速くメモリを大量に積んでいる機械を選ぶことから始める。CPU はそのままコンパイルの時間に直結するので、速いものを選ぶ。メモリは多い方がいいのは一般論だ。それに加えて、メモリを酷使する高速化をするので、メモリは普段以上に大切になる。

二番目に、何を更新するかを決める。ここでは build 段階のディスクの使用状況で後々の構成が変わる。そのため、どれくらいのディスクが必要か目安を付けておく。mozilla/firefox/seamonkey などのブラウザや、X11 関連、Gnome と KDE 関連は特にディスクを使うので気を付けなければならない。他にも JDK も大量に使う。

三番目に、portsupgrade の手法を決める。具体的には -i などを使ったりして、一つ一つを指定していく方法か、-R や -r 等を使い一気に大量に更新するかを決めるのである。

四番目に、環境設定を始める。まずは /etc/make.conf に WRKDIRPREFIX を設定する。私の環境では、ports 関連は全て /ports に独立して入れてあるので、/ports/tmp が一番適当である。work が WRKDIRPREFIX 以下に作られるので、make clean の必要性が低くなる。次に TMPDIR を設定する。/tmp を mount_mfs で、作り TMPDIR=/tmp とする。gcc は TMPDIR に一時ファイルを作成するので、それらのディスクへの書き込みを抑制するためである。

五番目は、ports のインストールされるパーティションを mount -u -onoatime を付けて再マウントする。これにより、ファイルのアクセス時刻が更新されなくなるので、pkg_delete や install 時のディスク負荷が軽減される。一般的には /usr であろう。uyota 流だと /ports になる。

六番目は、予行練習である。portupgrade -n で、何が更新されるか把握すること。ports を大量に更新するとなると build が止まったり、ファイルが消されていて fetch が出来なかったり、依存関係が壊れていて、install できなかったりと、なにかと問題は付き物である。そこで、一度の更新は十個程度に留めておくのが、backup、動作点検、そして高速化には都合がいい。

七番目はメモリ容量の点検である。自分の機械に入っているメモリの量はわかっていると思う。top を見ると


last pid:   979;  load averages:  0.01,  0.02,  0.08    up 0+01:02:52  20:48:29
64 processes:  2 running, 61 sleeping, 1 stopped
CPU states:  0.0% user,  0.0% nice,  0.6% system,  1.2% interrupt, 98.2% idle
Mem: 104M Active, 144M Inact, 73M Wired, 336K Cache, 58M Buf, 156M Free
Swap: 1024M Total, 1024M Free

などと出てくる。ここで、Active は現在利用中のものである。そして、Wired に割り当てられているものは、他に流用することは出来ない。そのため、それらをメモリの容量から引いたものが実質的に ports を更新するのに使える物である。C++ などだと、大体コンパイルで 100MB ぐらいメモリを使うものがある。そこで更に 100MB を引くのである。ここ機械は 512MB 入っているので、大体 250MB ぐらいである。この領域を可能ならば md デバイスとして使うことにより高速化をするのである。

八番目にやるのは、WRKDIRPREFIX に必要な容量の評価である。大型の ports を途中介入なしでやるのであれば、WRKDIRPREFIX に新しいパーティションをマウントする。この場合、それなりの規模のディスク容量が必要になる。マウントするのは mdconfig -t vnode でもよい。この場合、出来る高速化は make clean を抜かす位である。しかし、それに見合うディスク容量が必要となる。

次は、小型の ports を大量だが途中介入なしで入れたり、大型であっても、上で計算したメモリよりも少ないときは、迷わずに mdconfig -a -t swap を WRKDIRPREFIX にマウントする。 また、portupgrade に -w -W を付けて、make clean は行わない。make clean などという、遅い作業は待つ必要は無い。この構成が出来ると、ディスクに書き込むのは install の時だけになる。この方法は、大量の perl や ruby 等のスクリプトや、linux 関連のコンパイルを必要としない ports が特に高速になる。

このとき、メモリの容量が少し足りないくらいでも、気にしないで、mdconfig -t swap をしても構わない。もし、普通のディスクであれば全てを書き込まなければいけないのである。md-swap であれば、メモリが足りなくなったときのみに、ディスクに書かれるだけである。この手法は、特にメモリが大量にある場合に有効である。このことを踏まえると、大量に一気に更新する場合にも、メモリが大量にある場合は md-swap も使える。しかし、make clean がかかるようにしておかないと、disk full になってしまう。make clean による遅延と、md-swap による高速化のどちらが有効かは場合によるので一概にはいえない。

次は、大型の ports を -i などで、一時停止しながら入れる場合である。この場合は、メモリが多少少なくても、停止中に掃除できるので、250MB もメモリに余裕があればほとんど問題なくできる。portupgrade -w -W でやるので、md-swap が段々と大きくなってくるのである。そして、ある程度の大きさになったら、rm などをせずに newfs をする。


# umount WRKDIRPREFIX
# mdconfig -d -u X
# mdconfig -a -t swap -s 1g
# newfs -U /dev/mdX
# mount /dev/mdX WRKDIRPREFIX

をやる。ports の make clean 自体も遅いが、rm もとても遅いのである。rm は全ての i-node を更新するのであるから。newfs、特に UFS2 は少し空っぽのブロックを書くだけなので、何千倍から何万倍と速いのである。mdconfig -d をすると、一度メモリが開放されるので、これも余計な swap-in を減らすことができる。

さて、最後の九番目としては、portupgrade の計画が全て整ったことになる。あとは、計画に基づいて portupgrade を進めていくだけである。一番影響があるのは、八番目に決めた、WRKDIRPREFIX の部分である。 そうだ、最初のコンパイルが始まったら、portupgrade -F で pre-fetch をするのも忘れないように。

ports の更新でやはり時間を食うところは、コンパイルである。残念ながら、そこは CPU を変えるぐらいしか、劇的な速度向上は見込めない。しかし、これだけ手を加えるとそれ以外の部分はかなりの高速化が見込めるのである。

かなり、複雑な手順のように見えるが、試してみるとそうでもないと思う。時に linux_base や linux-sun-jdk など、展開が主で、ディスクの負荷がほとんどの物を扱うときには、劇的な効果が見込める。次回は、例を取り上げるのと、最適化の結果を上げたいと思う。