newLISP
女房が日頃お世話になっている クックパッド、おいらも間接的にお世話になってるのは、 言うまでもありません。あんた作る人、オイラー食べる人って言う役割分担がしっかりと 確立してる訳です。
そんじゃ、オイラーの役割は? そりゃ、Lispじゃなかった、Listに決まってるでしょ。 紙の上のリストに、カッコは付いていませんが、Listと看做して、買い物へGoですよ。
今回は、クックパッドの人達の頭の中を覗いてみるって事で、 COOKPAD Engineers'Blogを見ています。 この人達は、Web屋さん? Ruby屋さん? ipadとかのアプリ開発者? ああ、ドロドロイドの ググル端末も相手にしてるんか?
よりどりみどりで、httpの波に乗ってる人達の生態が垣間見れて面白い。
オイラーは、Rubyのレールで挫折した口ですから、Web系とは一定の距離を保っていて、 JavaScriptなんて、めっそうもないと思ってます。
なんてったって、JavaScriptはブラウザーのためのアセンブラですからね。 最近、 「ECMAScript 6」が正式に承認される って事で、一層激しい、バトルが繰り広げられる事でしょう。一般ユーザーは、高みの 見物をしてればいいんです。中の人になろうなんて、考えない事ですな。
昨夜のTVで、ほや の事を取り上げていた。俗に海のパイナップルと呼ばれるやつ。 定番は、ほや酢だろうけど、ほやの炊き込みご飯とか、ほや入り石巻焼きそばとか。
クックパッドに敬意を表して ほやで検索したら、73品のレシピが出てきたぞ。 一番レシピを検索しよと思ったら、会員登録せいとな。クックパッドさんも商売だから しょうがないか。つらつら見てくと、ほや玉子なんてのがある。番組で、ほや爆弾とか 言って紹介してたやつだな。
オイラーとほやのファーストコンタクトは、東北新幹線の中だった。先輩と東北出張した時、 帰りの車中ですっかり気が緩み、酒でも呑むかとなった。つまみは何にする? 社内販売の おねーさんに勧められるものをチョイス。その中に、ホヤの干したものが入っていた。
これで、やみつきになりましたねぇ。それ以来、自宅呑み用に買い求めていたよ。そして、 仙台転勤で、本格的にほやと接するようになる。駅前にある市場で、パイナップルを 買ってきて、酢の物やらてんぷらとか食していたぞ。冷酒に最高。あーー、久しぶりに 食べたくなったぞ。
newLISP 落穂拾い実験
前回出来心でOpenBSDを入れ、釣られてnewLISPも入れてしまった。これも何かの縁。 みなさん、newLISPされてるかと思って探ってみると、
余り人気が無いみたい。でも、オイラーにはピピィと来るものがあるので、追従実験と言うか 落穂拾い実験をやってみる。
newLispは簡易的なWebサーバーになれるそうだ。実験してみる。newlispをportsで コンパイルして、残骸を残しておいたので、そこの中のexampleに移動する。 そこを、apache風に言うとhtdocsとみなして、起動する。-dだと、ずっと居残り。-pだと、 一回接続しただけで、終了するハカナイサーバーになる。
[ob: examples]$ newlisp -http -d 8080 -w `pwd`
index.cgiはお約束。env.cgiもapacheのtestに良く使ったものなので、今回は、form.html。
[ob: ~]$ w3m http://localhost:8080/form.html Post or Get Variables name=This is Name checkMe=on color=Blue chooseOne=Third textField=text1%0D%0Atext2%0D%0A%0D%0A Command line ("newlisp" "./form.cgi") Environment Variables STY=8894.ttyp0.ob SERVER_SOFTWARE=newLISP/10.6.2 DOCUMENT_ROOT=/usr/ports/pobj/newlisp-10.6.2/newlisp-10.6.2/examples LOGNAME=sakae NEWLISPDIR=/usr/local/share/newlisp-10.6.2 HTTP_USER_AGENT=w3m/0.5.3 SHELL=/bin/ksh TERM=screen HTTP_HOST=localhost:8080 :
づらづらと、printenv相当も出てきたぞ。
次なる実験は、簡単に実行ファイルが出来るって話。schemeのあの人が聞いたら羨むぞ。
コマンドラインから入力した引数を大文字にするやつ
[ob: z]$ cat hoge.lsp ;; Link example (println (upper-case (main-args 1))) (exit) [ob: z]$ newlisp -x hoge.lsp fuga [ob: z]$ chmod 755 fuga [ob: z]$ ./fuga 'hello newlisp' HELLO NEWLISP
で、その正体は?
[ob: z]$ ldd fuga fuga: Start End Type Open Ref GrpRef Name 15577000 35580000 exe 1 0 0 fuga 0dfe0000 2dfe9000 rlib 0 1 0 /usr/lib/libm.so.9.0 04cef000 24cf8000 rlib 0 1 0 /usr/lib/libreadline.so.4.0 040f4000 24104000 rlib 0 1 0 /usr/lib/libncurses.so.14.0 0e3ad000 2e3dc000 rlib 0 1 0 /usr/lib/libc.so.78.1 076f3000 076f3000 rtld 0 1 0 /usr/libexec/ld.so [ob: z]$ file fuga fuga: ELF 32-bit LSB shared object, Intel 80386, version 1, for OpenBSD, dynamically linked (uses shared libs), not stripped [ob: z]$ size fuga text data bss dec hex 317982 8712 3384 330078 5095e
出来上がった実行ファイルサイズとnewlispサイズを何となく比べてみた。
[ob: z]$ newlisp -e '(- 373489 373412)' 77 [ob: z]$ wc hoge.lsp 4 10 77 hoge.lsp
fugaの方が77バイト大きい。もしやと思って、hoge.lspのサイズを調べてみると、77バイト。 多分、newlispの最後にくっつけているだけじゃねぇ。manには、
-x source target Link the newLISP executable with a source file to built a new target executable.
偉そうにLinkって書いてあるけど。。。裏を取ってから、そういう発言をしましょう。 強引にファイルの尻尾を表示してみる。
[ob: z]$ tail fuga : _netService;; uppercase.lsp - Link example (println (upper-case (main-args 1))) (exit)
やっぱりね。と言う事は、本体とhoge.lspをcatしちゃえば同じ事。実際にやってみると、 (zzzって名前で作った)replが動いちゃって駄目だった。
何処が違うか、確認してみる。diffを使いたいんだけど、バイナリーファイルだって言われて 拒否されたんで、代わりにcmpを使いました。
[ob: z]$ cmp -l fuga zzz 323848 244 46 323849 262 46 323850 5 46 323851 0 46 323852 115 100 323853 0 100 323854 0 100 323855 0 100
ファイルの323848目から、8バイト分が違うとな。ファイルの尻尾にスクリプトが くっついているから実行ヨロってマークなんだろうな。実際にどうか実物確認。
[ob: z]$ hexdump -s 323847 -C fuga 0004f107 a4 b2 05 00 4d 00 00 00 00 00 00 00 00 00 00 00 |....M...........| 0004f117 00 00 00 00 00 00 00 00 00 28 73 65 74 20 28 67 |.........(set (g| 0004f127 6c 6f 62 61 6c 20 27 6d 6f 64 75 6c 65 29 20 28 |lobal 'module) (| : [ob: z]$ hexdump -s 323847 -C zzz 0004f107 26 26 26 26 40 40 40 40 00 00 00 00 00 00 00 00 |&&&&@@@@........| 0004f117 00 00 00 00 00 00 00 00 00 28 73 65 74 20 28 67 |.........(set (g|q :
差分が8バイトって事だけど、おいらのオンボロ32Bitマシンだと、2種のデータだな。 目印っぽいのがzzzに見える。一方、fugaの方は、intデータが2個だな。えと、16進数を 10進に直してみるか。
で、取り出したのは、dcというunixに備え付けのRPN計算機。こいつは、入力と出力にそれぞれ 独自に進数を設定出来る。(起動時は、入出力都も、10進数扱い)。16 i で、入力を16進数扱いに 設定してから、表示させてみる。リトルエンディアンに気をつけて、上位桁から入力。
[ob: z]$ dc 16 i 5B2A4 p 373412 4D p 77
最初のintは、373412、次は77って、ソースのサイズじゃん。最初のデータは、データを 格納してるファイルオフセットだろう。裏を取る。
[ob: z]$ hexdump -s 373411 -C fuga 0005b2a3 00 3b 3b 20 75 70 70 65 72 63 61 73 65 2e 6c 73 |.;; uppercase.ls| 0005b2b3 70 20 2d 20 4c 69 6e 6b 20 65 78 61 6d 70 6c 65 |p - Link example| 0005b2c3 0a 28 70 72 69 6e 74 6c 6e 20 28 75 70 70 65 72 |.(println (upper| 0005b2d3 2d 63 61 73 65 20 28 6d 61 69 6e 2d 61 72 67 73 |-case (main-args| 0005b2e3 20 31 29 29 29 0a 28 65 78 69 74 29 0a 0a | 1))).(exit)..|
次は、検証だな。ウブ15.04に舞台を移して、ソースからコンパイルした。libffi.hとreadline.hが 無いぞと言う、Linuxからの嫌がらせを跳ね除けて、無事にインストール完了。
見るべきソースは、newlisp.c。-xっていうアーギュメントを頼りに、場所を特定すると、
if (strncmp (argv[idx], "-x", 2) == 0) { if (argc == 4) linkSource (argv[0], argv[idx + 1], argv[idx + 2]); exit (0); }
頭から、linkSourceを探すと、目印が出てきた。
void linkSource (char *, char *, char *); char linkOffset[] = "&&&&@@@@";
続いて関数本体を見ると、見るに耐えない八苦の後があったので、indent を使って 整形したよ。
newlispを一気読みして、@@@@ が見つからなかった、既にリンクされてるって事で、何も しない。そうでなければ、
offset = (ptr - buffer - 8); *(int *) (buffer + offset) = (int) size; *(int *) (buffer + offset + 4) = (int) sourceLen; writeFile (target, buffer, size, "w"); readFile (source, &buffer); writeFile (target, buffer, sourceLen, "a");
bufferにnewlispが読み込まれた状態で、2つのデータ(size,sourceLen)を書き換え、ソースを 追加してる。以上、解析終了。じゃ、片手落ちなんで、gdbにかけてみる。題材は、先ほどの 例のようにして作った fuga。元になるnewlispは、コンパイルの最後にstripされてしまうので、 沢山あるmakefile_xxxx のうち、makefile_build で、stripを無効にしておいてから作り 直したものを使う。
軽くソースをみて、loadFileがあれなんで、
sakae@uB:~/newlisp-10.6.2$ gdb -q fuga Reading symbols from fuga...done. (gdb) set args 'hello world' (gdb) b loadFile Breakpoint 1 at 0x8053620: file newlisp.c, line 3417. (gdb) run Starting program: /home/sakae/newlisp-10.6.2/fuga 'hello world' Breakpoint 1, loadFile (fileName=0x80aa3fa <linkOffset+5> "", offset=908700, linkFlag=1, context=0x808c653) at newlisp.c:3417 3417 { (gdb) bt #0 loadFile (fileName=0x80aa3fa <linkOffset+5> "", offset=908700, linkFlag=1, context=0x808c653) at newlisp.c:3417 #1 0x08053897 in loadStartup (name=<optimized out>) at newlisp.c:577 #2 0x0804b175 in main (argc=2, argv=0xbffff064) at newlisp.c:785 (gdb) c Continuing. HELLO WORLD [Inferior 1 (process 2311) exited normally]
ふむ、おらましは分かった。もうすこしloadFileの中を覗くと、肝は、 evaluateStreamって事だな。そして核心は
1373 eval = evaluateExpression ((CELL *) program->contents);
後は、猿が喜びいさんで、皮を剥くように、剥いていけばいいんだな。 compileExpressionなんてのも通ってるけど、ひょっとしてJITしてる? 興味は尽きないな。
猿にらっきょう(カッコよく言うと、エシャロット)を与えると、皮の中に実が有ると思って どんどん剥いていくそうです。そして何も出てこないので怒り出すとか。オイラーは、 Lispの御本尊が、cellだと思っているので、拝んでおきましょうかね。
typedef struct { UINT type; void * next; UINT aux; UINT contents; } CELL;
ちょっと不思議な感じがするので、consでも追ってみれば良いかな。
(gdb) bt #0 p_cons (params=0x893549d0) at newlisp.c:5298 #1 0x1b7ba387 in evaluateExpression (cell=0x893549b0) at newlisp.c:1520 #2 0x1b7c144f in evaluateStream (stream=0xcfbe3db0, outDevice=2, flag=0) at newlisp.c:1278 #3 0x1b7c257c in executeCommandLine (command=0x800a7f70 "(cons 1 2)", outDevice=2, cmdStream=0xcfbe421c) at newlisp.c:1227 #4 0x1b7c3116 in main (argc=1, argv=0xcfbe46a4) at newlisp.c:899
いきなりp_consなんて、どうして知った? それは、primes.hを見たからさ。このLispには、 昔ながらのcar,cdrが無い。代わりは、first,restが務めているんだけど、旧人類には寂しいな。 この登録簿をいじって、car,cdrも追加しとくか。単にaliasとして追加するだけなら、 protos.hに手を入れる必要は無いかな。
実際にやってみた。primes.hにcarなりを登録するだけ。
{"first", p_first, 0}, {"car", p_first, 0},
こんあ具合。
> (car '(a b c)) a > (cdr '(a b c)) (b c)
後は、山ほどある cxxr を、init.lspに登録しとけばいいんか。
OpenBSDで、動いているnewlispにgdbからアタッチしようとしたら、権利無いと言われた。 権利はどうやって、手に入れる? man ptraceしたら書いてあった。
[ob: newlisp-10.6.2]$ sudo sysctl kern.global_ptrace=1
永久にそうしたいなら、/etc/sysctl.conf に書いておけばいいんだな。
話題は変わって、面白い機能で、libcとかが使える。
sakae@uB:~$ newlisp newLISP v.10.6.2 32-bit on Linux IPv4/6 UTF-8 libffi, options: newlisp -h > (import "libc.so.6" "printf") (printf "hello\n") printf@B74E4130 hello 6 >
printfの返値って、出力した文字数だっけ? なお、replで複数行を入力する時は、 プロンプトの所で、一度RETを叩いてから入力。実行は、もう一度空行を入力する。 慣れないと、戸惑うな。
guiserver
リナちゃんにnewLISPを入れたついでに、OpenBSDでは動かなかった、guiserverを試してみる。 普通にLISPとかでGUIをやろうとすると、やれTcl/Tkだとか、今ならOpenGLとかだと、土台が 有りすぎて困る。
そこを一挙に解決したのがJavaで絵を書かせればいいじゃん、です。さすが、newLISPの 開発者が元SUNの人らしい。説明書では、純正Java(JRE)を使えってなってるけど、今なら Open-jre8で大丈夫でしょう。
入れて、newlisp-editを起動したら、真っ白なBOXが出てくるだけでした。そんな事ってあるの? 使ってるウィンドウマネージャをawesomeからLXDEにしたら、無事に動いた。こういう事も あるのね。
src玉には、guiserver.jarが付属してるんで、それをそのまま使えばいいんだけど、Java-dirの 中には、javaファイルが多数鎮座してた。これらからjarファイルを作るとなると、antとかmebinだったかの 悪名高いものを使わないといけないのかな?
いえいえ、そんな事はないよ。make 一発でOKみたい。
javac -Xlint java/*.java mv java/*.class . jar cmf Manifest guiserver.jar *.class images/*.png
隠れzipコマンドで、クラスファイルと画像ファイルをまとめるだけでいいんだ。
OpenBSDで動かないの、javaファイルを探れば、何かヒントが得られるかしらん。それとも、 tcpdumpあたりを使って、どんなパケットが飛んでるか調べてみる方が簡単?
ちゃんと動いてる、ウブあたりのものと比べると、一発で判明するかな? GUI回りは 余り興味が沸かないので、気が向いたらやってみよう。
newLISP bench
newLISPって速いの? そんな疑問は普通に湧いてくる。その答えがFAQに載ってた。 Benchmarking newLISP PerlやPythonと比べているのは、そういうスクリプト族と対抗しようって魂胆だな。 そういうのは、尽きないね。
でも、言語は自由に選べばいいんでねぇ。それよりオイラーが気になるのは、OSの違いで どんな差が有るかだな。ソースからnewLISPを入れると、ベンチマークが手軽に出来るようになってる。 品質保証を兼ねたテストケースの一環で環境が提供されてるんだ。
VMWARE上に入れた各種OSで、実行してみた。使ったパソコンの石のスペックは貧乏人御用達の セルロンって事ぐらいしか知らないので、OpenBSDのdmesgの結果を載せておく。多分、この 辺は、cpuidの引き写しでしょうから、実の石を反映してると思われ。
cpu0: Intel(R) Celeron(R) CPU 900 @ 2.20GHz ("GenuineIntel" 686-class) 2.20 GHz cpu0: FPU,V86,DE,PSE,TSC,MSR,PAE,MCE,CX8,APIC,SEP,MTRR,PGE,MCA,CMOV,PAT,PSE36,CF LUSH,DS,ACPI,MMX,FXSR,SSE,SSE2,SS,NXE,SSE3,SSSE3,XSAVE,HV,PERF,ITSC real mem = 267862016 (255MB) avail mem = 251109376 (239MB)
次の表がOSバトルの結果。試合と言えば、思い出される事が有る。LLゴング。LLって軽い言語の略ね。 一年に一度、LLを愛する者達が集まってお祭りが有るんだ。今は傍流になっちゃったPerlを 始めPythonやらRubyやらPHPの自慢とかが繰り広げられるんだ。
それぞれの言語で与えられた課題をどう書くなんてのが有って、ネットに接続してWebページを 取ってくる(だったかな)なんてのが出た。PHPはページを作るのは得意だけど、そっち方面は 全く不得手。出題者はサドかと思っちゃったぞ。
試合なんで、本当にリングに上がって戦う訳ですよ。ある年に、新木場にある試合場が会場に なりました。スチール製の折り畳み椅子に1日中座るのは苦痛でしたが、面白かったた。 今年もLL 2015が、その会場で開かれるようです。 行かれる方は、座布団等を持参された方が宜しいかと思いますよ。
OpenBSD 5.7 12027.644 4.65 FreeBSD 10.1 4825.622 1.89 Ubuntsu 15.04 11732.084 4.53 Fedora 22 4492.617 1.77 MacOSX 10.9 2580 1.00
結果は、MacOSX 10.9, 2.3GHz Intel Core i5の実行時間を基準にして、何倍遅いか比が 表示されるようになったので、実行時間を逆算して載せておいた。
ウブはみんなが喜びいさんで使うので、もっとチューニングされてるかと思ったけど、newLISPに 関しては、遅い環境なのね。 上の値は、make bench した2回目の結果を載せてあるんで、たまたま運が悪かったのかな?
そんな疑いがあろうかと、何回か測定して、平均、標準偏差と、それを正規化したのを 載せておく。qa-benchの最後を修正。統計関数が標準で付属してるのは嬉しいぞ。
(set 'ttl '()) (dotimes (n 5) (set 'ttl (cons (bench) ttl))) (println "total: " ttl) (module "stat.lsp") (set 'mean (stat:mean ttl)) (set 'std (stat:sdev ttl)) (println "mean: " mean) (println "std: " std) (println "std/mean " (mul 100 (div std mean))) (exit)
浮動小数点を含む演算は、それ専用の関数があるので、間違えない事。例えば、 mulの代わりに * を使ってもエラーにならず、黙ってZEROを返してくるぞ。
まずは、ウブで実験
sakae@uB:~/newlisp-10.6.2/qa-specific-tests$ ./my ->pretty-print total: (5384.369000000001 5424.540999999999 5303.790999999998 5340.063 5274.904000000004) mean: 5345.5336 std: 60.23136654439582 std/mean 1.126760601493475
ふむ、先の結果11.7秒ってのは、たまたまだったんでしょうかね。5.3秒ってのを公式記録と しておきますかね。ついでに、fedoraも測っておくか。
[sakae@fedora qa-specific-tests]$ ./my ->pretty-print total: (4793.733000000005 4612.539999999998 4490.250000000001 4603.153000000003 4524.587000000001) mean: 4604.852600000001 std: 117.5947625674717 std/mean 2.553713935761412
微妙にfedoraの方が速いな。でも、ばらつきはfedoraの方が大きいんで、リアルタイムOSと しては、ウブに軍配が上がるな。>そんな重箱の隅をつつくなよ。
生データの少数点以下の桁数が、無駄に大きいな。最小桁はナノ・セコンドって無意味じゃん。 丸めてしまえ。(cons (round (bench) -1) ttl) なんていう風にすると、少数点以下の一桁 だけに丸めと言うか切捨てられるぞ。
-http オプション
newLISPは簡易的なWebサーバーに成れることを上で確かめた。どういう風に実現してる? etagsを設定して、すいすいソースを飛び回ってみた。昔は、このためにglobalとかを使って わざわざ閲覧システムを作ったけど、今はこれで十分。
newlisp.cの中のexecCommandLine関数の中に、こういう部分がある。
if (!batchMode) { if (logTraffic == LOG_MORE) writeLog(command, TRUE); #ifndef LIBRARY if (strncmp(command, "GET /", 5) == 0) executeHTTPrequest(command + 5, HTTP_GET); else if (strncmp(command, "HEAD /", 6) == 0) executeHTTPrequest(command + 6, HTTP_HEAD); else if (strncmp(command, "PUT /", 5) == 0) executeHTTPrequest(command + 5, HTTP_PUT); else if (strncmp(command, "POST /", 6) == 0) executeHTTPrequest(command + 6, HTTP_POST); else if (strncmp(command, "DELETE /", 8) == 0) executeHTTPrequest(command + 8, HTTP_DELETE); #endif else if (!httpMode) goto EXEC_COMMANDLINE; return; }
ここで、httpコマンドを確定させて、後は、nl-web.c内へ飛び、同関数の中で、DOCUMENT_ROOT等の 環境変数をセットしてから、それぞれの処理をしてるとな。
Pythonとかだと、httpサーバーは、言語外でライブラリーで処理してるけど、こちらは 組み込んである。勿論、必須のbase64の関係も内蔵してるよ。
わざわざ組み込みにしてるのは、作者さんがWebサーバーの勉強をしたかったから? それとも、LLリングで負けないように、スループットを確保したかったから? そうだ、apache付属の ベンチマークツール ab で、apacheに試合を挑んでみるか? どうだ、やるか?
いえ、今回は、ご遠慮しときますです。