2015/03/12(木)kv-0.4.18

前回からほとんど時間が経っていませんが、kv-0.4.18にアップデートしました。

0.4.17で、lgammaにdouble以外の型を入れるとコンパイルできないバグを混ぜてしまっていたので修正しました。いつもならもう少し修正が貯まってから公開するのですが、某所でlgammaが必要だったのですぐに公開しました。ついでに、第1種Bessel関数(整数次のみ)の簡単な実装を追加しました。

2015/03/07(土)kv-0.4.17

久しぶりに、kvライブラリを0.4.17にアップデートしました。

主な変更点は、
  • 特殊関数の説明文を書いた。特にガンマ関数の計算方法の詳細を書いた。
  • 内部で例外として使っていたrange_errorをdomain_errorに変更。
  • 数学関数を含む常微分方程式が正しく解けないことがあったバグを修正。
などです。

2番めの例外についてちょっと補足しておきます。区間演算やaffine arithmeticなどでゼロ除算や負の数の平方根などが出てきたとき、プログラムをただちに停止せず例外をthrowするようになっています。そして、常微分方程式や全解探索などの上位プログラムでは解くべき問題の関数評価をするときにtry-catchでこの例外をcatchし、例外が起きてもすぐに諦めずにステップ幅を小さくしたり区間を分割したりするような実装になっています。この例外として、0.4.16以前はrange_errorという例外を使っていたのですが、どうやらdomain_errorの方がふさわしそうなので、プログラム全体でdomain_errorを使うように変更しました。

普通に使ってるだけならあまり関係のない変更ですが、例えばaffine.hppをコピーして改造して使っているような方がいれば、同じように変更した方がいいでしょう。単に一括置換するだけで大丈夫ななずです。

忙しい1月2月を過ぎて、やりたいこともいろいろ溜まっているのでここから頑張ろう。

2015/01/27(火)INTLAB version 9の重要性

INTLABは、ドイツのRump先生が作成されている、matlab上で動く精度保証付き数値計算のためのパッケージ集です。
(なぜかページが2つあって微妙に中身が違うので両方載せておこう。)

まったく予想してなかったのですが、このINTLABがversion 9にアップデートして、新たにOctaveに対応したそうです。Octaveは、フリーの有名なmatlabクローンです。と言っても、完璧な互換性を目指してるわけでは無さそう。

INTLABはRump先生の大変な努力で作成された素晴らしいソフトウェアで、この分野のデファクトスタンダードと言えるものです。ところが、最近はきちんと動かないことが多く、周囲の研究者に聞くと、matlabのversionを2012aで留めておかないと、いろいろまずいことがおきるのだそうです。

これは、matlabのせいと言うよりは、matlabの中で使っているBLASの問題です。

BLAS(=Basic Linear Algebra Subprograms)は、LAPACKから呼び出される行列積などの基本的なプログラムの集合体です。近年の計算機では計算速度に比較して相対的にメインメモリが遅くなっており、キャッシュメモリを使ってその遅さをカバーしています。そのため、行列積などのプログラムはキャッシュのヒット率を極限まで高めるきわめて技巧的なプログラミングが要求され、高速なプログラムは誰でも書けるものではなくなってしまいました。実際、素人が書いた単なる三重ループとBLASの持つ行列積とでは速度が10倍以上違うのが普通です。また、CPUの性能を極限まで引き出すにはそのCPUに合わせたプログラムが必要になります。そのため、行列積などの基本的なプログラムはインターフェースだけ揃えておいて中身は自由に差し替えられるように分離するようになり、それがBLASです。

最近のmatlabで使っているBLASはIntel Math Kernel Libraryに含まれているもので、これの挙動が精度保証に使っている手法と合わなくなってしまったため、きちんと精度保証が出来なくなってしまったというわけです。

より詳しく説明します。浮動小数点数(double)を係数に持つ行列A,Bがあったとき、行列積ABを精度保証付きで計算する問題を考えます。三重ループで行列積を計算するプログラムを元に、計算過程を全て区間演算に置き換えれば精度保証付きの結果を得ることが出来ます。ところが、このようなことをすると高速なBLASの恩恵にあずかることが出来ません。そこで、
  1. 丸めの向きを下向きに変える
  2. C1=ABをBLASで計算する。
  3. 丸めの向きを上向きに変える。
  4. C2=ABをBLASで計算する。
  5. (丸めの向きを元に戻す)
という手順で区間行列[C1,C2]を得るというテクニックを用いて、丸めの向きの変更回数を極限まで減らし、またBLASの高速性を利用します。ところが、この方法は、
BLASの中でマルチスレッドあるいはマルチコア計算を行っており、他のスレッドまたはコアに丸めの向きの情報が伝わらない
ことがあって、この場合は精度保証されていない結果が得られてしまうことになります。また、
BLASの中でStrassenアルゴリズムなどの「減算を含む」計算が使われている
場合は、「計算式の中の全ての計算が下向き丸めならば最終的な計算結果が下向き丸めになる」という仮定を満たさなくなってしまい、このケースも精度保証が破れてしまいます。Strassenアルゴリズムはある程度大きい行列でないと効果がないことが知られていますが、計算機の発展に伴い大きな問題が解けるようになってきてそろそろStrassenアルゴリズムが有効に機能する段階に入りつつあると考えられています。

上記のような精度保証の破綻は、自分でBLASを書いたならば当然そういうことがありえるのかどうか判断できますが、closed sourceな他人の書いたBLASの場合はそうはいきません。Intel Math Kernel Libraryはまさにclosed sourceであり、2012年に(何があったか窺い知ることは出来ないが)何らかの変更が行われ精度保証を破綻させてしまったというわけです。

さらに考えてみれば、2012aまでのmatlabでも「観察の結果問題が無いように見える」だけであり、実は精度保証が破綻しているケースがあるかも知れません。使っているアルゴリズムも分からずソースコードも見られないプロダクトを使って精度保証が出来ると考えるほうがどうかしてるとさえ思えます。

さて、ここまでお読みいただければ、INTLABがOctaveに移植されたことの重要性がよく分かると思います。Octaveはopen sourceであり、(インストールの仕方にも拠るでしょうが)使われるBLASもatlasやopenblasなどのopen sourceなものです。中身を見れば精度保証に問題があるかどうか判断できるし、問題があるならば修正することも出来ます。

INTLABはversion 7から有償になってしまったので簡単に試せないのが残念ですが、Octaveでどのくらいちゃんと動くものか、近いうちに試してみたいと思います。

※上に書いたような問題点は、ずっと前から感じていたことですが、代替手段が無かったこともあり、あまり明確に否定してしまうと線形計算の精度保証の分野の研究者が困ると思い、あまり口にしないで来ました。INTLABがOctaveで動くようになったことで、書くなら今しかないと思い、この文章を書きました。

2014/12/25(木)kv-0.4.16

kvライブラリを0.4.16にアップデートしました。

今回は特に大きなバグが見つかったわけではありません。
  • Smithの定理を使ったDKAの精度保証版を作ってみた。
  • gamma関数を少し整備した。
  • defint-singular.hppの中にひっそりとある端点特異性を持つ関数の積分に、ステップ幅を自動調節するversionを追加した。
が主な変更点です。

某所でLaguerre関数を含む凶悪な(?)数値積分が話題になり、その計算できちんと精度を出すためにいろいろと試行錯誤した結果が反映されています。

この積分を通じて、kvライブラリの欠点が見えてきました。
  • この積分は全体を超高精度で行えばちゃんとまともな精度保証結果が出ることは分かっているが、それは現実的に無理。
  • 高精度が必要なのは全体のある一部分だが、kvライブラリはworking precisionを全体で一斉に変えることを想定していて、一部分だけ計算精度を変える混合精度計算が書きづらい。
ということです。すぐに対策するのは難しいかも知れない。どうすればいいんだろ。

2014/12/16(火)C99のfma関数の挙動を調査

以前、

HaswellとFMA

の記事で、Haswellに新しく実装されたFMA命令について書きました。某Mさんがたまたまfmaという命令がmath.hにあるのを見つけてくれてその存在を知ったので、どういうものなのか調査してみました。かなりややこしいのですがせっかくなので記憶が薄れないうちに記録を残しておきます。

まず、fmaという命令はC99で追加されていて、使うときにはmath.hが必要らしいです。そこで、
#include <stdio.h>
#include <math.h>

int main()
{
        double u, x, y, z;
        int i;

        u = 1;
        for (i=0; i<52; i++) u /= 2;

        x = 1+u;
        y = 1-u;
        z = -1;

        printf("%.15g\n", fma(x, y, z));
}
というプログラムを作って、その挙動を観察してみました。

まずコンパイルの可否。次の表のようになりました。
オプション無し-lm-mfma
gcc4.1xxx
gcc4.4xox
gcc4.8xoo
この結果を受けて、コンパイル出来た3通りについて、-staticで静的リンクした上でIvyBridge(FMA命令を持たない)とHaswell(FMA命令を持つ)で実行してみました。
IvyBridgeHaswell
gcc4.4 -lm0-4.93038065763132e-32
gcc4.8 -lm-4.93038065763132e-32-4.93038065763132e-32
gcc4.8 -mfma実行できず-4.93038065763132e-32
u=2-52として(1+u)(1-u)+(-1)を計算しているので、
  • FMA命令がちゃんと機能していれば、真の値-2-104が、
  • FMAでなくただの乗算+加算だと、1-2-104が1に丸められ、それに(-1)を足すので0が、
結果となるはずです。

これらの実験結果を見ると、次のような推定が出来ます。
  • gcc4.1では、C99のfma関数のサポートそのものが無い。
  • gcc4.4では、C99のfma関数が実装され、libm内にfma関数が存在する。-mfmaによるHaswellのFMA命令の有効化は実装されていない。libm内のfma関数は、CPUがFMA命令を持っていればそれを使い、持っていなければ単にfma(a,b,c)=a*b+cを実行する。
  • gcc4.8では、-mfmaによるfma命令の有効化が実装された。これを付けてコンパイルすると、CPUがFMA命令を持つことが前提のコードが生成され、main関数中のfmaはlibm呼び出しではなく直接HaswellのFMA命令にコンパイルされる。
  • gcc4.8で更に注目するべきは、libm呼び出しの場合IvyBridgeでも何故か正しい値が計算されたことである。これは普通ではなかなか出来ないはずで、CPUがFMA命令を持たなかった場合は(例えばmpfrを用いた)正確なFMA命令のエミュレーションを行うような実装が行われたと思われる。
この最後の推測を裏付けるため、10億回実行させた場合の実行時間(秒)を計測してみました。
IvyBridgeHaswell
gcc4.4 -lm2.462.236
gcc4.8 -lm127.422.828
gcc4.8 -mfma-2.214
50倍ほどの速度の低下が認められ、何らかのエミュレーションが行われていると推定出来ます。
OK キャンセル 確認 その他