libmのpow()関数を高速化改造する
FreeBSDのpow()関数とpowf()関数(べき乗)がとてつもなく動作が遅い事に気が付いた。
ひたすらpow()するだけのプログラムのFreeBSD版とLinux版を用意してFreeBSDで両方を動かしてみると、何故かLinux版の方が速い。
しかもFreeBSD版は倍の時間が掛かる。
/* * コンパイルは「gcc hoge.c -lm」とやる。 */ #include#include int main() { double i, j, k = 0; for (i = 0.0001; i <= 0.2; i += 0.0001) for (j = 0.0001; j <= 0.2; j += 0.0001) k += pow(i, j); printf("%f\n", k); }
何で遅いのかソースファイルを探してみると、該当ソースは/usr/src/lib/msun/srcにあるe_pow.cとe_powf.cと分かる。
FreeBSDのpow()関数はC言語で書いてあるが、glibcのソースを見てみると、こっちはpow()関数をアセンブラで(浮動小数演算命令を使って)書いてある。
ここでかなり差が出てると思われるので、glibcのソースをFreeBSDにマージして高速化してみる。
以下作業手順
具体的なやり方
★ファイルのコピー
glibcにあるe_pow.Sとe_powf.Sを/usr/src/lib/msun/i387にコピーする。
e_pow.Sとe_powf.Sはglibc-2.3.6/sysdeps/i386/fpuある。
★ソースを編集
コピーしたe_pow.Sとe_powf.Sをエディタで編集する。
「#include
#define ASM_TYPE_DIRECTIVE(name,typearg) .type name,typearg; #define ASM_SIZE_DIRECTIVE(name) .size name,.-name #define ALIGNARG(log2) log2
「ENTRY(__ieee754_pow)」、「ENTRY(__ieee754_powf)」をそれぞれ「ENTRY(pow)」、「ENTRY(powf)」にする。
これは必要なのか検証してないが、何となく回りのソースに合わせた。
一番下の「END(__ieee754_pow)」とEND「(__ieee754_powf)」を削る。
★Makefileの編集
/usr/src/lib/msun/i387/Makefile.incをエディタで編集する。
下の方に、「ARCH_SRCS+= e_pow.S e_powf.S」を加える。
# long double counterparts
ARCH_SRCS+= s_ceill.S s_copysignl.S s_floorl.S s_scalbnl.S s_truncl.S
# ここに付け加える
ARCH_SRCS+= e_pow.S e_powf.S
LDBL_PREC = 64 # XXX 64-bit format, but truncated to 53 bits
★libmをコンパイル&インストール
下記の通りにコマンドを入れるとインストールされる。
# cd /usr/src/lib/msun # make obj && make depend && make && make install
元に戻したいなら、Makefile.incを編集し直して、/usr/obj/usr/src/lib/msunを消してからコンパイルし直す。
★比較
Celeron 2.66GHzマシンで最初の方に書いてあるのプログラムを動かしたタイム。
上が改造前、下が改造後のタイム。
倍以上のスピードアップに成功してる。