Inside newLISP (5)
『コンピューターがネットと出会ったら』(角川学芸出版)なんて本を読んだ。坂村健先生が 監修なさった本。坂村先生ったら、あのTRONの提唱者だ。
当時、アメリカはこのOSに脅威を感じ、さかんに潰しにかかってきていた。そう、やつらは 知っていた。パソコン用のOSなんかはM$に任せておけば安泰だけど、遠い将来に、溢れるコンピュータは 色々な物に組み込まれるに違いない。そのOSを日本に押さえられるのは、非常な脅威になると。
今や、世に存在するコンピュータの90%は、いろいろな物(車、家電等々)に組み込まれている。 それがネットに繋がるようになったらどうよ?
記憶に新しい事として、2011年の地震の時、HONDAがカーナビの情報から通行出来た道路を 収集、それをググルと協力して通行可の道路MAPとして公開した。普段ならこういう事は、 プライバシー権を楯に許される事じゃなかったろうけど、非常時と言う事で、非常な快挙 だったのではなかろうか。激同意。
IoTって言葉が持て囃されている。IT業界のバズワードかと思うと、さにあらず。語源は 昔、P&G(迷惑な香料入り洗剤を作っている所。 臭い公害は田舎の香水で充分) の流通管理革新に重役会議が有った時に、招かれた教授が、重役連中にも分かるように 、物がインターネットに繋がれば、便利ですよと説明したのに端を発しているとか。
そう、RFIDで流通革命しましょってやつね。みんな管理されてるよね。スイカとかパスポートとか 運転免許証とかで。そして、データは誰の物てんで、JRが日立だかにスイカのデータを売っちゃったもの だから、高木先生とかが吼えたのは、記憶に新しい。
技術の問題よりも制度の問題が非常に大きくて、進歩が停滞してるってのが有るんだけど、 それを慣らそうってんで、物に番号を振る事になれるように盛んにコマーシャルしてるね。 そう、国民一人一人に番号を振っちゃえってやつ。来年からスタートするのかな。そのうちに、 希望者はRFIDを埋め込んであげますよ。そうすれば、パスポートを忘れて空港でおろおろする 事も無いし、免許不携帯で罰金取られる事も無くなりますよ、とな。近未来に実験が始まるだろう。
で、人に番号を振るだけじゃなくて、あらゆる物や場所にも番号を振っちゃえってのを 坂村先生は唱えていて、国際的に承認された。 ucodeとはとか、 ITproまとめucodeが詳しい。
で、問題になるのが、Last one hop 物とネットの接点ね。無線でやるか光でやるか、 今のIPv4じゃ無理、だったら、IPv6だな。電気喰わない簡易なやつね。
という事で、これまた先生が、6LowPANってのを提唱。RFCにして、これどうよ?って 投げている。
[sakae@fb10 ~]$ rfc -l 6282 : Abstract This document updates RFC 4944, "Transmission of IPv6 Packets over IEEE 802.15.4 Networks". This document specifies an IPv6 header compression format for IPv6 packet delivery in Low Power Wireless Personal Area Networks (6LoWPANs). The compression format relies on shared context to allow compression of arbitrary prefixes. How the information is maintained in that shared context is out of scope. This document specifies compression of multicast addresses and a framework for compressing next headers. UDP header compression is specified within this framework.
そして、通信プロトコルはやっぱりみんなが慣れてるhttpでしょ。でも、重くてね。 だったら軽いの考えようぜ。 CoAP( Constrained Application Protocol)とは 。これもRFC7252で、世に問われています、ってさ。
現在は、やれクラウドだ機械学習だとやたら、コンピューターパワーの部分が進化してる けど、入り口と出口の部分はこれからってのを強く認識した次第。
そう、Lisp屋ならずとも、read eval printと3拍子揃って初めて実用になるんよ。 そして、境界面をオープンにしておかないと、拡がらないのも道理。よく分かりましたよ。
最後に、組み込みOSの50%を押さえていると豪語してる TRONプロジェクト を挙げておく。いろいろな所に飛べて便利だよ。
primes
前回、rubyとcrystalで素数を求める勝負をしてみた。折角なので、newLISPも参戦させてみる。 今回 arrayとやらを使ってみる。
listとほぼ同じように使えるんだけど、全く同一に扱えるかと言うと微妙に違っていたりする。 例えば、dolistが使えないとか。どうしても使いたかったら、array-listでlistに変換するとかしないと だめだ。
それから、arrayで宣言した時、nilで初期化される。よって、rubyのコードは正論理で書いて あるけど、newlispでは、それをそのまま生かすべく、負論理で勝負してみた。
篩の部分は、取ってつけたようにdefineで囲ってあるけど、これはdebugする時、引数に 関数を渡す約束になってたから、そうしたまでで、特に意味は無い。
(setq N 100000000) (setq sieve (array (+ N 1))) (setf (sieve 0) true) (setf (sieve 1) true) (define (hoge) (setq i 2 to (sqrt N)) (while (<= i to) (unless (sieve i) (setq j (* i i)) (while (<= j N) (setf (sieve j) true) (inc j i))) (inc i))) (hoge) (setq i 0) (while (< i N) (unless (sieve i) (println i)) (inc i)) (exit)
実行してみると
sakae@uB:~/z$ time newlisp primes.lsp >/dev/null Killed real 0m24.307s user 0m0.560s sys 0m5.060s
OSによってメモリー喰いの迷惑千万なプロセスは殺されたんだな。 考えてみれば、1セルで20バイト消費するんだった。それを100M個も要求してるんで、 メモリーに載せようとすると、2Gも必要になる。今も昔もLispはメモリー大食い!
検索範囲を1/10の10Mまでに縮小して実行すると
sakae@uB:~/z$ time newlisp primes.lsp > /dev/null real 0m19.073s user 0m15.808s sys 0m3.072s
こうなると、shiroさんコンピュータ搭載のgaucheではどうなん?って疑問が、ふつふつと 湧いてきます。上のコードをgosh用に書き換えて、実測。
(define N 10000000) (define sieve (make-vector (+ N 1) #f)) (vector-set! sieve 0 #t) (vector-set! sieve 1 #t) (define (primes) (define i 2) (define to (sqrt N)) (define j 0) (while (<= i to) (unless (vector-ref sieve i) (set! j (* i i)) (while (<= j N) (vector-set! sieve j #t) (inc! j i))) (inc! i))) (primes) (define i 0) (while (< i N) (unless (vector-ref sieve i) (print i)) (inc! i)) (exit)
まあ、コードは似たようなものだな。
sakae@uB:~/z$ time gosh 4gosh.scm > /dev/null real 0m4.445s user 0m4.328s sys 0m0.112s sakae@uB:~/z$ crystal build sieve.cr Using compiled compiler at .build/crystal sakae@uB:~/z$ time ./sieve > /dev/null real 0m2.304s user 0m2.004s sys 0m0.296s sakae@uB:~/z$ time ruby sieve.cr > /dev/null real 0m12.917s user 0m12.584s sys 0m0.260s
gosh速えぇーーー。
Killed
OpenBSDで上記のメモリー喰いプログラムを実行しTopで監視すると、
load averages: 1.33, 0.49, 0.21 ob.ring.net 06:16:39 18 processes: 1 running, 16 idle, 1 on processor CPU states: 0.2% user, 0.0% nice, 99.8% system, 0.0% interrupt, 0.0% idle Memory: Real: 143M/238M act/tot Free: 2684K Cache: 12M Swap: 259M/259M PID USERNAME PRI NICE SIZE RES STATE WAIT TIME CPU COMMAND 4639 sakae 35 0 754M 57M run - 0:05 10.69% newlisp-10. :
swapを使い果たすのはいいんだけど、OSが暴走する。メモリーを要求した時に、もうあげないって 言われないのかな。言われたらメモリーを使い果たしたエラーを出して、newLISPが自殺する と思うんだけど、そんな素振りも無い。OSはけなげにメモリーを割り当てようとして、よからぬ 事が起きてしまったんだろうな。これの防止って、ulimitで制限をきつくする事なのかな?
しょうがないので舞台をFreeBSDに移してやってみる
[sakae@fb10 ~/t]$ newlisp primes.lsp Killed [sakae@fb10 ~/t]$ echo $? 137
何やら、エラー番号が返ってきたけど、なんじゃらホイ。確かこの番号は128げたを履いているんだった。 細かい事は、wait()なりwaitpid()で子プロセスの返すstatusを得られるから、それをミレ。
[sakae@fb10 ~]$ kill -l 137 KILL
って、まんまの案内じゃん。
こういう時は/var/log/messagesに 何か出てないか調べてみるんだな。最近、このファイルを覗く事なんて無いから、すっかり 忘れていたよ。
Sep 3 07:44:28 fb10 kernel: swap_pager: out of swap space Sep 3 07:44:28 fb10 kernel: swap_pager_getswapspace(16): failed Sep 3 07:44:29 fb10 kernel: pid 712 (newlisp-10.6.2), uid 1001, was killed: out of swap space
なる程、swapエリアの不足で、OSから刺客が送られて、殺されたんか。それにしても、この 情報はshellに伝わらないのかな。やけにあっさりしたメッセージしか表示しないな。
折角なんで、上記メッセージを出してる所を特定してみるか。
[sakae@fb10 /sys]$ find . -name '*.[ch]' -exec fgrep 'out of swap space' -H {} \; ./vm/swap_pager.c: * about to run out of swap space, using lowat/hiwat hysteresis. ./vm/swap_pager.c: printf("swap_pager: out of swap space\n"); ./vm/vm_pageout.c: killproc(bigproc, "out of swap space");
いえねぇ、上記の検索技、最近覚えたので使ってみたかっただけかも。findで探しあてたファイルを execでfgrepに繋ぐ。ファイル名は -H の後ろにあるへんてこりんな文字の組み合わせ。 ーHは、ファイル名を表示せよ、だな。これを知る前は、バッククォートでファイルを集めて いたんだけど、しばしば引数が長すぎるぜエラーを喰らっていたんだ。
[ob: sys]$ find . -name \*.[ch] | xargs grep 'main('
こちらの方が、もっと簡単でっせ、と、天から慶事です。
killprocってぴったりなやつが有った。それは、kern/kern_sig.cに定義されてた。
killproc(p, why) struct proc *p; char *why; { PROC_LOCK_ASSERT(p, MA_OWNED); CTR3(KTR_PROC, "killproc: proc %p (pid %d, %s)", p, p->p_pid, p->p_comm); log(LOG_ERR, "pid %d (%s), uid %d, was killed: %s\n", p->p_pid, p->p_comm, p->p_ucred ? p->p_ucred->cr_uid : -1, why); p->p_flag |= P_WKILLED; kern_psignal(p, SIGKILL); }
/var/log/messagesに出すログは、ここで生成してるとな。所で、Killを受け取ったプロセスは 強制終了するって事だけど、どうやって実行されるんだろう?
unixカーネルの設計本をちょっと紐解いてみる。1999年の8月に買った本。あの頃はunixの 事良く分からなかったけど、今になって再読したら分かるかなあ?
関係のありそうな所を拾い読み。7.3 プロセスの終了の所
プロセスはexitシステムコールに実行する事によって終了する。exitで終了したプロセスは、 ゾンビ状態に入り、使用していた資源を解放する。構文は、exit(status)
処理方法を指定しないシグナルを受け取った場合、カーネルは内部的にexitを呼び出す。その 場合statusの値はシグナル番号になる。
本棚を覗いた時、1990年に買った、MINIXオペレーティングシステムなんて本も目に入ってきた。 タンネンバウム教授の教育用OSの解説本だ。リナス君がこれで勉強して、効率悪いじゃんって 教授と喧嘩し、だったら自前のOS作ってやるわいってんでLinuxが生まれたのは余りに有名。
ソースリストが本に載ってるんだけど、ちと追うのが大変だな。どこかにソースないかしら ってんで探してみたら、 パッケージ MINIX and Bochs (全てのリリース) で、公開してた。MINIXはLinuxの一枚板とは違って、Plan9風にモジュール構造になってるけど、 それぞれの部分は、大いに参考になるだろう。
もう一冊有った。Lions' Commentary on UNIX 6thEdition オイラーも昔、 unix/v6ってんでやってたね。
大分、脱線しちゃったんで、newLISPに戻ろう。
array
rubyのそれをnewLISPに移植する時、arrayが使われていたんで、newLISPにもarrayが あるかと探したらあったんでそのまま使った。もし無かったら、アレーと叫んでいた事で しょう。何てったって、途中のアクセスがO(1)で出来る。書き換えも同様。
listだとそうはいかずにアクセスはO(n)になるし、書き換えはlistのコピーが伴うので とんでもない事になる。綺麗な事では右に出るものがないHaskellでさえ、背に腹は変えられずに アレーを、アレーと言う程サポートしてますからね。
> (array 3) (nil nil nil) > (array 3 2 '(true 1 "hoge")) ((true 1) ("hoge" true) (1 "hoge"))
基本は、サイズを与えると、nilで初期化されたのが返る。初期値を与えたい時は、最後に指示する。 同一のtypeじゃなくても、文句言われない所が偉いぞ。ってか、包容力が大き過ぎる気が しないでもないですが。。。
コードを追うと、arrayはlistの仲間だって事で、nl-list.cの中のp_arrayに飛んでくる。
1663=> array = makeArray(index, 0); 1664 1665 if (list != nilCell) 1666 array = initArray(array, list, &next); 1667 1668 return (array);
コードの前半部分は、引数の処理。indexと言う17要素の配列に、ディメンジョンのサイズが セットされる。listは初期化の値だ。
(array 10)ってのを評価した時、index配列は
(gdb) p index $7 = {10, 0, -2065313788, ...]
2次元以降は無いので、0で終端してる。まるで、C語の文字列みたいな扱い。 makeArrayの第二引数は、ディメンジョン番号(最初はゼロ)を与えている。 ディメンジョン番号を増やしながら、再帰呼び出しするんで、わざわざ番号を渡してる。 初期値は、nilで埋められて、ディメンジョン番号が0以外の時は、copyCellしてる。
1665行のifは、初期値が有る場合の上書きだ。よって、なるべくなら、初期値を与えない方が、 スピードアップに寄与すると思われ。 おいらの野生の感も冴えてるね。
> (time (array 10000)) 0.503000 > (time (array 10000 '(1 2 3 4 5 6 7))) 1.076000
大きなarrayを作る時は、アレーって程、効いてくるぞ。以上検証終わり。
makeArrayの中では何をやっているかと言うと
1680=> array = getCell(CELL_ARRAY); 1681 size = index[p]; 1682 array->contents = (UINT) callocMemory(size * sizeof (UINT) + 1); 1683 array->aux = size * sizeof (UINT) + 1; 1684 addr = (CELL * *) array->contents; 1685 1686 p++; 1687 if (index[p] > 0) { : 1693 } else 1694=> while (size--) 1695 *(addr++) = copyCell(nilCell); 1696 1697 return (array);
エレメントの個数分+1の連続領域を取ってきて、その領域の先頭を保存後、nilCell(のアドレス)を 書き込んでいる。
次は、アレーの特定なindexを書き換える。p_setfの役目だ。(内部的にはsetqも同じルーチンを使う) p_setfの冒頭付近にある、evaluateExpressionで、対象になるアドレスを割り出す。
1554 /* implicit indexing array */ 1555 else if (pCell->type == CELL_ARRAY) { 1556 if (!sPtr) 1557 sPtr = symbolCheck; 1558=> result = implicitIndexArray(pCell, args->next); 1559 symbolCheck = sPtr; 1560 pushResultFlag = FALSE; 1561 }
上記のimplicitIndexArrayが呼ばれて
1996 while (cell->type == CELL_ARRAY) { 1997 addr = (CELL * *) cell->contents; 1998 size = (cell->aux - 1) / sizeof (UINT); 1999 if (index < 0) 2000 index = index + size; 2001 if (index >= size || index < 0) 2002 return (errorProcExt2(ERR_ARRAY_INDEX_OUTOF_BOUNDS, stuffInteger(index))) ; 2003=> cell = *(addr + index); 2004 if (params == nilCell || cell->type != CELL_ARRAY) 2005 break; 2006 params = getIntegerExt(params, (UINT *) & index, evalFlag);
対象のなるcell(アドレス)が取り出される。後は、このアドレスの内容を破棄し、 新しい値で置き換える。返値として、新しい値を返す。
GambitC
ふとOpenBSDにgambitC schemeが入っている事を思い出した。scheme語そのままでも 走るし、スクリプトをコンパイルしておいて走らせる事も出来る。crystalの先輩格だね。 登場して貰おう。
思い出したよ。バイナリーの作り方。そのままgsiで実行も出来る。
[ob: t]$ gsc -o run -exe 2gsi.scm
走らせてみると、余り大きな数だとヴェクターが溢れちゃうみたいで駄目。
[ob: t]$ time gsi 2gsi.scm > z *** ERROR IN ##make-vector -- Heap overflow 0m0.09s real 0m0.02s user 0m0.05s system [ob: t]$ time ./run > z 0m0.84s real 0m0.46s user 0m0.38s system [ob: t]$ time gsi 2gsi.scm > z 0m8.82s real 0m7.18s user 0m1.63s system
また、/dev/nullに棄てるのはOpenBSDでは駄目だったので、ファイルに落としてる。 ウブでは、そんな事無かったので、OpenBSDとの相性なんですかね。
使ったスクリプトは下記。gambitに無い手続きを、追加してる。inc!の増分値無しも 定義したかったんだけど、方法が分からなかったので、ちょっとみっともない事に なってる。
(define-macro (inc! var n) `(set! ,var (+ ,var ,n))) (define-macro (unless test . body) `(if ,test #f (begin ,@body))) (define-macro (while test . body) `(do () ((not ,test)) ,@body)) (define N 2000000) (define sieve (make-vector (+ N 1) #f)) (vector-set! sieve 0 #t) (vector-set! sieve 1 #t) (define (primes) (define i 2) (define to (sqrt N)) (define j 0) (while (<= i to) (unless (vector-ref sieve i) (set! j (* i i)) (while (<= j N) (vector-set! sieve j #t) (inc! j i))) (inc! i 1))) (primes) (define i 0) (while (< i N) (unless (vector-ref sieve i) (print i "\n")) (inc! i 1)) (exit)