float and double
切れるOS
キレるOSじゃなくて、コネクションが切れてしまうOSの事ね。
古いThinkpadを Debian + FreeBSD のディアルブートにして、1月半程経過した。朝にdebianを使えば、午後はFreeBSDをブートって具合に、ほぼ平等に使ってる。
で、Debianの方なんだけど、時々セッションが重くなり、そのうちにWiFiの接続が切れてしまうんだ。そんな時は、再びsshで接続しなおして、tmux a とかして難を逃れている。
なんでや? 重くなるって事を考えると、通信ロスが頻発して、再送を繰り返しているんではなかろうか? 何とか証拠を掴みたいものだ。
その点、FreeBSDの方は極めて安定。まだ一度もそのような現象にみまわれた事は無い。ただ一つ難があるとすれば、起動時にWiFiのセッションが確立せず、30秒ぐらいの時間を於いての再接続で繋る事。一度電波を掴まえれば後は問題無いんで、安心していられる。
やっぱり、安定、安心のFreeBSD だな。
あれ? OpenBSDはどうした?
obusb$ doas syspatch doas (sakae@obusb.hoge.jp) password: Get/Verify syspatch69-016_sshd.tgz 100% |***************| 1938 KB 00:01 Installing patch 016_sshd Get/Verify syspatch69-017_x509.tgz 100% |***************| 15582 KB 00:05 Installing patch 017_x509 Errata can be reviewed under /var/syspatch
おお、patchが降ってきた。そろそろ次の版が出る季節なんで、先行のバックポート・パッチの提供なんだな。
x509の方は兎も角、sshdが気になる。
OpenBSD 6.9 errata 016, Sep 27, 2021: sshd(8) failed to clear supplemental groups when executing an AuthorizedUsersCommand or AuthorizedPrincipalsCommand helper program. Apply by doing: signify -Vep /etc/signify/openbsd-69-base.pub -x 016_sshd.patch.sig \ -m - | (cd /usr/src && patch -p0) And then rebuild and install sshd(8) cd /usr/src/usr.bin/ssh make obj make make install
WiFiも安定しているし、これぞ本当に安心して使えるOSだ、な。
ああ、忘れていた事がある。このdual-bootなマシン、言わば敷地一杯までののよくばり2世帯住宅なんだけど、世帯間を結ぶ通路が無いんだ(完全な設計ミスって認めるよ)。お届け物は、Windowsを介してしか出来無い。すこぶる不便。
なんとかしたいんで、リナのfs(5)を見たんだけど、そんなの知らないって態度。大人のFreeBSD側からなら、何とかなるかな。
検算
前回作った、LC回路の同調周波数を計算するやつ。それぞれのOSで実施
sakae@pen:~$ gosh ;; Debian at amd64 gosh> (use mine) gosh> (resf 1e-6 500e-12) 7117625.5
[sakae@fb ~]$ gosh ;; FreeBSD at i386 gosh> (use mine) gosh> (resf 1e-6 500e-12) 7117625.300323632
結果が違うじゃん、って事で、Gauche本のTESTの章を参考に、何も考えずに、Hz以下は無視していいでねぇって事にしちゃったんだ。
(define (nearly-equal? a b) (< (abs (- a b)) 1.0)) (test* "test-mine-resf" 7117625 (resf 1e-6 500e-12) nearly-equal?)
でも、真値はどこにあるの? 素直にC言語で確認。
[sakae@fb ~]$ ./a.out 7117625.5000
これ、FreeBSDでC言語の結果だ。そうなると、FreeBSDのgauche-packageの挙動がおかしい? 一応、第3者委員会の意見も聞いてみましょうかね。なるべくC言語のコンパイラーから遠い所に居る、独立機関の方がいいな。
[sakae@fb ~]$ maxima ;;; Loading #P"/usr/local/lib/ecl-20.4.24/sb-bsd-sockets.fas" ;;; Loading #P"/usr/local/lib/ecl-20.4.24/sockets.fas" Maxima 5.44.0 http://maxima.sourceforge.net using Lisp ECL 20.4.24 Distributed under the GNU Public License. See the file COPYING. Dedicated to the memory of William Schelter. The function bug_report() provides bug reporting information. (%i1) 1.0 / (6.283185307179586 * sqrt(1e-6 * 500e-12)) ; (%o1) 7117625.434171771
FreeBSDではeclて言う組み込みCLを土台にしてた。
sakae@pen:~$ maxima Maxima 5.44.0 http://maxima.sourceforge.net using Lisp GNU Common Lisp (GCL) GCL 2.6.12 Distributed under the GNU Public License. See the file COPYING. Dedicated to the memory of William Schelter. The function bug_report() provides bug reporting information. (%i1) 1.0 / (6.283185307179586 * sqrt(1e-6 * 500e-12)) ; (%o1) 7117625.434171771 (%i2) fpprec:50; (%o2) 50 (%i3) bfloat(1.0 / (6.283185307179586 * sqrt(1e-6 * 500e-12))); (%o3) 7.117625434171770699322223663330078125b6 (%i4) bfloat(1.0 / (6.283185307179586 * sqrt(1b-6 * 500b-12))); (%o4) 7.1176254341717708622248965831569037806983783971197b6
入力式 %i3 は、少数点の変換で精度落ちしてるから、間違いだ。%i4式が、余す所なく、50桁まで、信用出来る。
Debianでは、GNU一族になったGCLを使ってるな。なんか系列企業な小会社みたいだな。これぞ、社会の縮図だ。
それは、さておき、両maximaでは同じ結果になった。値をFreeBSD,Debianと比べると、まあ、真ん中っぽい。果たして何が真値?
参考まで、Debian(i386)に固定し、次の比較をした
S式での定義 モジュール版(so) C言語(a.out) 7117625.434171771 7117625.5 7117625.300324
インタープリタ 対 コンパイラ 対 …
gauche-packageを使って用意した関数って、gccでコンパイルされた結果だよな。結果的に、みんなのあこがれchez風になってる訳だ。じゃ、普通にインタープリタ上で定義したやつと比べてスピードどうよってのが、頭をよぎる。
まずは、コンパイラ版
(use gauche.time) (use mine) (time-this 10000000 (lambda () (resf 1e-6 500e-12))) #<time-result 10000000 times/ 1.191 real/ 1.190 user/ 0.000 sys> (time-this 10000000 (lambda () (resf 1e-6 500e-12))) #<time-result 10000000 times/ 1.218 real/ 1.210 user/ 0.000 sys>
時間計測用のモジュールが用意されてたので、それを使って1千万回実行。
(use gauche.time) (use math.const) (define (resf L C) (/ (* 2pi (sqrt (* L C))))) resf (time-this 10000000 (lambda () (resf 1e-6 500e-12))) #<time-result 10000000 times/ 3.172 real/ 3.170 user/ 0.000 sys> (time-this 10000000 (lambda () (resf 1e-6 500e-12))) #<time-result 10000000 times/ 3.198 real/ 3.200 user/ 0.000 sys>
インタープリタ上で定義して実行。約 2.6倍の差がついた。
それじゃ、もう一つ getpid をmypidって言うscheme名で呼び出すやつ。前回は偏屈な理由でって煙に卷くような理由を付けたけど、これが出来るか確認したかったのさ。
(time-this 10000000 (lambda () (mypid))) #<time-result 10000000 times/ 9.826 real/ 8.210 user/ 1.600 sys> (time-this 10000000 (lambda () (mypid))) #<time-result 10000000 times/ 9.825 real/ 8.050 user/ 1.760 sys>
えらい時間がかかってますなあ。と思ったら、根底はシステムコールなんだね。とっても重い呼出って事が、火を見るより明らかだ。
そんじゃ、お手軽に生getpidを使ってみる。gauche-c-wrapperね。これが本当にruby-ffiの対抗馬です。gauche-packageの方は、スタティックな挙動。それに対してc-wrapperはダイナミックな挙動になる。両者にどれぐらいの差が出るか、興味津々ですよ。
sakae@deb:/tmp/mine$ gosh (use gauche.time) (use c-wrapper) (c-load-library "libc") #<undef> (getpid) *** ERROR: unbound variable: getpid Stack Trace: ____________________________ at "(standard input)":4 1 (eval expr env) at "/usr/share/gauche-0.97/0.9.10/lib/gauche/interactive.scm":303 (c-include "sys/types.h") (c-include "unistd.h") (getpid) 1635
使うライブラリィの宣言だけでは、当然怒られた。ちゃんとmanして、インクルードファイルも教えてあげましょう(幾つもあると、うんざりする事間違いなし)。
(time-this 10000000 (lambda () (getpid))) #<time-result 10000000 times/ 15.797 real/ 11.830 user/ 3.910 sys> (time-this 10000000 (lambda () (getpid))) #<time-result 10000000 times/ 14.627 real/ 10.560 user/ 4.020 sys>
5割増しで遅くなった(カーネル側の消費時間が大幅に増加してるけど、何に油売ってるのだろう)。常用するなら、やっぱりドラエモンのポケットだね(意味不なら、前回の記事を参照)。
mine.so 観光
お決まりの観光をしてみる。その前に、
sakae@deb:/tmp/mine$ nm mine.so 00001330 t minelib_mypid 00004160 d minelib_mypid__STUB 00001350 t minelib_resf 000041a0 d minelib_resf__STUB 00001300 t minelib_test_mine 00004120 d minelib_test_mine__STUB 00001230 T res 00001280 T test_mine U getpid@GLIBC_2.0
自分で書き下した関係者がこんな風に、反映されてた。
sakae@deb:/tmp$ gdb -q gosh Reading symbols from gosh... (No debugging symbols found in gosh) (gdb) b resf Function "resf" not defined. Make breakpoint pending on future shared library load? (y or [n]) y Breakpoint 1 (resf) pending. (gdb) r Starting program: /usr/bin/gosh [Thread debugging using libthread_db enabled] Using host libthread_db library "/lib/i386-linux-gnu/libthread_db.so.1". gosh> (use mine) gosh> (resf 1e-6 500e-12) Breakpoint 1, resf (l=9.99999997e-07, c=4.99999986e-10) at ./mine.c:9 9 float tmp = 6.283185307179586 * sqrt(l * c);
関数に渡される引数の精度ってこんなものなの? goshが何かやってないかな? 後で確認。
(gdb) bt #0 resf (l=9.99999997e-07, c=4.99999986e-10) at ./mine.c:9 #1 0xb7fc83c6 in minelib_resf (SCM_FP=0x9519c, SCM_ARGCNT=2, data_=0x0) at ./minelib.stub:9 #2 0xb7bd0b76 in ?? () from /lib/libgauche-0.97.so.0 #3 0xb7be47a5 in ?? () from /lib/libgauche-0.97.so.0 #4 0xb7be559a in Scm_ApplyRec () from /lib/libgauche-0.97.so.0 #5 0xb7be5ae6 in ?? () from /lib/libgauche-0.97.so.0 #6 0x0040445d in enter_repl () #7 0x00402dba in main ()
そして呼出履歴。aptから入れた本体はdebug情報が落されているんで、物足りないぞ。 呼出のためのスタブは、何か呪文っぽいな。disassemble して読んでみろって助言は、却下します。
(gdb) finish Run till exit from #0 resf (l=9.99999997e-07, c=4.99999986e-10) at ./mine.c:10 0xb7fc83c6 in minelib_resf (SCM_FP=0x9519c, SCM_ARGCNT=2, data_=0x0) at ./minelib.stub:9 9 (result (resf l c))) Value returned is $1 = 7117625.5 (gdb) c Continuing. 7117625.5
sakae@deb:/tmp$ gdb -q a.out Reading symbols from a.out... (gdb) b resf Breakpoint 1 at 0x11bb: file z.c, line 5. (gdb) r Starting program: /tmp/a.out Breakpoint 1, resf (l=9.99999997e-07, c=4.99999986e-10) at z.c:5 5 float tmp = 6.283185307179586 * sqrt(l * c); (gdb) finish Run till exit from #0 resf (l=9.99999997e-07, c=4.99999986e-10) at z.c:5 0x00401224 in main () at z.c:10 10 printf("%15.6f\n", resf(1e-6, 500e-12)); Value returned is $1 = 7117625.5 (gdb) c Continuing. 7117625.300324 [Inferior 1 (process 1916) exited normally]
出力の変換の所で、うまく丸めこまれている???
many scheme
色々なschemeが、どんな回答を返すか、下記のコードで確認してみる。
sakae@deb:/tmp$ cat resf.scm (define (resf L C) (/ (* 6.283185307179586 (sqrt (* L C))))) (display (resf 1e-6 500e-12)) (newline) (exit)
chez scheme
sakae@deb:/tmp$ scheme Chez Scheme Version 9.5.4 Copyright 1984-2020 Cisco Systems, Inc. > (compile-file "resf.scm") compiling resf.scm with output to resf.so
コンパイルすると、思わせぶりなsoファイルが出来るけど、unixとは全く関係ない。
sakae@deb:/tmp$ scheme resf.so Chez Scheme Version 9.5.4 Copyright 1984-2020 Cisco Systems, Inc. 7117625.434171771
こんな風に使うしかないのさ。
petite
chez schemeのインタープリタ版
sakae@deb:/tmp$ petite resf.scm Petite Chez Scheme Version 9.5.4 Copyright 1984-2020 Cisco Systems, Inc. 7117625.434171771
ちゃんと一致してる。
gsi
gambitのインタープリタ版
sakae@deb:/tmp$ gsi resf.scm 7117625.434171771
Gambit v4.9.3と言う版です。これのコンパイラ版が用意されてて、簡単にシングルバイナリーが作成出来る。
gsc
sakae@deb:/tmp$ gsc -exe resf.scm sakae@deb:/tmp$ ./resf 7117625.434171771 sakae@deb:/tmp$ ls -l resf -rwxr-xr-x 1 sakae sakae 57648 Sep 30 14:15 resf*
なかなか良い感じ!!
scm
sakae@deb:/tmp$ scm resf.scm 7.117625434171771e6
何故これらを選ぶのか?
schemeなら他にも色々あるだろう。guileとかracket(drscheme)とかchickenとか、星の数程ある。とても全部を試すって訳にはいかない。
オイラーの選択基準は、なるべく多数のOSでも動く事。gaucheは残念ながらOpenBSDでコンパイル出来無い。基準から外れる訳だけど、それに目をつぶれば、余りにもぬくぬく出来るから。今回みたいな事が出来るんで、益々楽しいな。
chez系は、世界的に有名だから。昔はBSD系でも動いたけど、シスコのお達しによりリナ系中心。かろうじてpetiteはFreeBSDでも動く。まあ合格の範疇かな。オイラーのつたない記事も各国からアクセスがあるしね。
gambitは、OSを選ばずって所が良い。ipadでも動くのは、もしもの時に遊び道具になってもらえそう。もしもって何だ?
scmは、オイラーのschemeへの入口だったから。初恋の人は忘れられないっていうじゃないですか。極めて情緒的な理由でした。
それより、論理だ論理だ。
float to double
と言う事で、重い腰をあげて、resf周りの宣言をfloatからdoubleに変更した。 まあ、最初に気付くべき事だけど、みんなに指摘されて、脳味噌が回転すると言うおそまつさ。
ちょっと弁明しとくと、とっかかりのGauche-glが、f32vectorを多用してた。そして、参考にした記事でもfloatを使ってた、と言う脳内CPU停止状態だったから。
Openglは昔の事だから、floatがデフォでメモリー節約設計だったんだね。それに追従すれば、f32vectorを使わざるを得ない。
type min max float 1.175494351e-38 3.402823466e+38 double 2.2250738585072014e-308 1.7976931348623158e+308
黙って double を使っておけば間違い無し。多数桁が欲かったら、maximaの多倍長浮動少数点数型を使え。
マシン固有のFPU
浮動少数点演算の同値判定って事で調べていたら、イプシロンなんていう定数があるそうな。 マシンに固有の設定になってる。OpenBSDだと
sakae@deb:/usr/src/OpenBSD_6.9/sys$ grep __FLT_EPSILON -rIl . ./arch/powerpc64/include/_float.h ./arch/arm64/include/_float.h ./arch/alpha/include/_float.h ./arch/mips64/include/_float.h ./arch/m88k/include/_float.h ./arch/sh/include/_float.h ./arch/i386/include/_float.h ./arch/amd64/include/_float.h ./arch/powerpc/include/_float.h ./arch/hppa/include/_float.h ./arch/arm/include/_float.h ./arch/sparc64/include/_float.h
なるほど、マシン毎にある。インテル入ってるなら、i387のコプロなハードな。
#define __FLT_RADIX 2 /* b */ #define __FLT_ROUNDS __flt_rounds() #define __FLT_EVAL_METHOD 2 /* long double */ #define __FLT_MANT_DIG 24 /* p */ #define __FLT_EPSILON 1.19209290E-07F /* b**(1-p) */ #define __FLT_DIG 6 /* floor((p-1)*log10(b))+(b == 10) */
1Bitの重みって事なのか。そして、仮数部24Bit,10進数だと6桁精度って読めばいいのかな。 80Bit仕様の定数もこのファイルに定義されてた。
sparc64だと、仮数部 113Bit,指数部33Bitなんていう機構もサポートしてるみたい。
F社はこれを捨てて、16BitバージョンのAI向けFPUを載せた、スパコン富嶽なんてのを作っているな。多数のFPUを同時に動かして、AI頑張りましょって作戦だな。
ちょっとびっくり
C言語でのresfをOpenBSDのgdbにかけた。
ob$ gdb -q a.out Reading symbols from a.out...done. (gdb) b resf Breakpoint 1 at 0x19f0: file resf.c, line 4. (gdb) r Starting program: /tmp/a.out Breakpoint 1, resf (l=8.7304488599225978e-61, c=0) at resf.c:4 4 double resf(double l, double c){ (gdb) bt #0 resf (l=8.7304488599225978e-61, c=0) at resf.c:4 #1 0x000003d6d7809a87 in main () at resf.c:10
えっ、こんな引数なの? びっくり仰天。
(gdb) n 5 double tmp = 6.283185307179586 * sqrt(l * c); (gdb) p l $1 = 9.9999999999999995e-07 (gdb) p c $2 = 5.0000000000000003e-10
ああ、引数が生えてきた。これ、gdbの個性??