テストが通らなかったことのメモ 続き
「64bit環境で通るテストが32bit環境で通らなかったことのメモ」の続き。なぜあのような計算結果になるかを調べてみた。もはや完全にJubatusとは関係ない。
前回も用いたコード。#1から#3の三つの計算をしている。
#include <cstdio> #include <cmath> double mixed_entropy(double e, double n) { if (n == 0.0) { return 0.0; } return log(n) - e / n; } int main(int argc, char** argv) { double n = 3; double e_d = 3 * log(3); double e_e = - e_d / 3 + log(3); double d = 0.0; d = e_e; fprintf(stdout, "d = %g\n", d); // #1 d = (n == 0.0) ? 0.0 : (log(n) - e_d / n); fprintf(stdout, "d = %g\n", d); // #2 d = mixed_entropy(e_d, n); fprintf(stdout, "d = %g\n", d); //#3 return 0; }
https://gist.github.com/y-tag/5127284
前回試したコンパイル環境と実行結果は以下。
記号 | コンパイル条件 | #1 | #2 | #3 | アセンブリ言語 |
---|---|---|---|---|---|
A | g++ 64bit | 0 | 0 | 0 | mixed_entropy.64.s |
B | g++ 64bit -O2 | 0 | 0 | 0 | mixed_entropy.64.O2.s |
C | g++ 32bit | 7.4051e-17 | -1.65883e-17 | -1.65883e-17 | mixed_entropy.32.s |
D | g++ 32bit -O2 | 0 | 0 | -1.65883e-17 | mixed_entropy.32.O2.s |
E | g++ 32bit -ffloat-store | 0 | 0 | 0 | mixed_entropy.32.store.s |
F | g++ 32bit -O2 -ffloat-store | 0 | 0 | 0 | mixed_entropy.32.O2.store.s |
G | clang++ 64bit | 0 | 0 | 0 | mixed_entropy.clang.64.s |
H | clang++ 64bit -O2 | 0 | 0 | 0 | mixed_entropy.clang.64.O2.s |
I | clang++ 32bit | 0 | 0 | 0 | mixed_entropy.clang.32.s |
J | clang++ 32bit -O2 | 0 | 0 | 0 | mixed_entropy.clang.32.O2.s |
以下は出力されたアセンブラやウェブ上で調べたことから推測したことである。そのため間違っているかもしれない。
32bit環境で-ffloat-storeオプションを用いた場合の結果(EとF)から、x87 FPUを用いて80bit長の計算をすると(CとD)、結果が0にならないことが推測される。-ffloat-storeを用いると80bit長のFPUから64bit長のメモリに値がロードされ、64bit長の計算を行っていることになるらしい。
一方で64bit環境(AとB)の結果が0になるのは、どうやらSSE2を用いて64bit長の計算をしているからのようで、出力されたアセンブリ言語を見るとxmmレジスタを用いてdivsdなどの計算を行っていた。32bit環境でclang++(llvm)を用いた場合も同様にSSE2を用いて計算していたようだ。
32bit環境で最適化のあるなし(CとD)を比較すると、最適化を行った場合(D)に#1と#2の結果が0となっている。これはコンパイル時の最適化で64bit計算が行われ、定数になったと推測される。実際に出力されたアセンブリ言語を見てみると、FPUにfldzで定数0をロードした後、fstlでメモリにストアして__fprintf_chkで表示するだけになっていた。
movl stdout, %eax fldz fstl 12(%esp) movl $.LC2, 8(%esp) fstps 32(%esp) movl $1, 4(%esp) movl %eax, (%esp) call __fprintf_chk movl stdout, %eax movl $.LC2, 8(%esp) movl $1, 4(%esp) movl %eax, (%esp) flds 32(%esp) fstpl 12(%esp) call __fprintf_chk