*lisp and bytecode
去年のお菓子をみている。
NetBSD上に最小限のデスクトップ環境を用意する+VNCでアクセスできるようにする
懐しいな。こういう設定を昔はよくやったものだ。
重力プログラミング入門「第2回:Pythonで重力波を解析する」
Python強いな。みんなで使えば怖くない。
最近のインストール事情らしい。まあ、pythonは使わないから、どうでもいい 事だけどね。
Welcome to Hy’s documentation!
とか言いながら、lispには目がないオイラーの事、早速入れてしまったぞ。
hy
実験なので試したのは、debian(32Bit)上のvboxで動かしているFreeBSD 12.0。 python環境はgdbを入れた時に一緒に入ったPython3.6。このままではpipが無 いので、py36-pipと使われるかも知れないgitを用意した。
$ pip-3.6 install --user hy Collecting hy Downloading https://files.pythonhosted.org/packages/77/77/5488c8ba77696f821591cbf4effb44671dcdb2a89162f4b9877d3a298d15/hy-0.15.0-py2.py3-none-any.whl (68kB) 100% |################################| 71kB 404kB/s Collecting clint>=0.4 (from hy) : Running setup.py install for args ... done Running setup.py install for clint ... done Running setup.py install for funcparserlib ... done Successfully installed appdirs-1.4.3 args-0.1.0 astor-0.7.1 clint-0.5.1 funcparserlib-0.3.6 hy-0.15.0 rply-0.7.6 You are using pip version 9.0.3, however version 18.1 is available. You should consider upgrading via the 'pip install --upgrade pip' command.
お約束でpipの新しいのが有るから入れてねってのが出てくる。お前いつもう ざいよ。
$ .local/bin/hy hy 0.15.0 using CPython(default) 3.6.7 on FreeBSD => (cons 1 2) Traceback (most recent call last): File "/home/sakae/.local/lib/python3.6/site-packages/hy/importer.py", line 199, in hy_eval return eval(ast_compile(expr, "<eval>", "eval"), namespace) File "<eval>", line 1, in <module> NameError: name 'cons' is not defined
lisp(の方言)なら、consぐらいは有るはずと思って試したら、早速エラーの洗 礼を受けてしまった。もどきである。と言うか、lisp風味のpythonって事だな。
=> (defn fact [n] ... (if (= n 0) ... 1 ... (* n (fact (- n 1))))) => (fact 10) 3628800
replはpythonのそれに移譲してるんだな。定義時に鍵括弧を使うのは、確固た る約束かな? 確かめてみる。
=> (defn hoge (x) (* x x x x x x x)) File "<input>", line 1, column 7 (defn hoge (x) (* x x x x x x x)) ^---^ HyMacroExpansionError: defn takes a parameter list as second argument => (defn hoge [x] (* x x x x x x)) => (hoge 9) 531441
鍵括弧を強制されるって、昔かの人が作ったarcみたいだ。あるいは、clojure 方言? 前回octaveを入れた別のFreeBSDに期待されないOpenJDK 8が来ていた ので、clojureする事もやぶさかではない。FreeBSDってOpenJDK 8までしか、 無いのが辛いけど。
=> (.strip "hoge ") 'hoge' => (setv val "fuga ") => (val.strip) 'fuga'
引数を前に置くか後ろに置くか、それが問題だ。 まあ、そういう悩みは、スパイで解決、、かな?
$ bin/hy --spy hy 0.15.0 using CPython(default) 3.6.7 on FreeBSD => (+ 1 2 3) 1 + 2 + 3 6
とか、
=> (defn fact [n] ... (if (= n 0) ... 1 ... (* n (fact (- n 1))))) def fact(n): return 1 if n == 0 else n * fact(n - 1) None
どちらが得かよーく考えてみよう。まあ、オイラーはlisp派かな。
python-sh なんてのを入れておくと
=> (import [sh [cat grep wc]]) => (-> (cat "/etc/passwd") (grep "-E" "^s") (wc "-l")) 7
パイプが使えます。これ面白いな。純粋Lispで実現できるのだろうか?
hycなんてコマンドも有るのね。何かと思ったら
Compile Hy code to Python bytecode. (defn hy-hy [name] (print (+ "Hy " name "!"))) (hy-hy "Afroman") $ hyc hyname.hy $ python hyname.pyc Hy Afroman!
さすが、みんなのPythonだけあるな。ドキュメントがしっかり有って、関係資 料も公開されてるな。
clojure
M-x cider-jack-in してから C-x C-e が基本だけど、ちょいと試すなら
lein repl :headless
か
clj -Sdeps '{:deps {nrepl {:mvn/version "0.4.5"} cider/cider-nrepl {:mvn/version "0.18.0"}}}' -m nrepl.cmdline --middleware "[cider.nrepl/cider-middleware]"
して、M-x cider-connect すれば良い。
sbcl
clojureも良いけど、やはり伝統のLispの方が馴染みがあるな。ならば、久し ぶりにslimeを入れてみるか。
(setq inferior-lisp-program "sbcl") (slime-setup '(slime-repl slime-fancy slime-banner))
以上、FreeBSDにたまたま入れた、octaveから派生したLispな環境でした。
bytecode
emacs 25.1.1の環境でskkを起動すると
skk-setup-modeline: Invalid byte opcode: op=183, ptr=5
こんなエラーが出てる。普段は emacs 26を使っているんで、bytecodeに齟齬 が生じているんだな。emacsの困ったちゃんであります。
旨い事に下記のような記事が出ていたので、楽しんでみる事にする。
toy-byte-code.el
;;; Commentary: ;; このファイル自体はバイトコンパイルしないでください ;; ;; foo.elc を単純に実行する ;; $ emacs --batch -l toy-byte-code.el -f eval-elc foo.elc ;; ;; foo.elc をトレース実行する ;; $ emacs --batch -l toy-byte-code.el -f trace-elc foo.elc ;; ;; trace-elc にスタックダンプ出力追加 ;; $ emacs --batch -l toy-byte-code.el -f dump-elc foo.elc
man emacs した所、バッチで動かすには、-l と -f が必須とな。前者は、動 かすスクリプト。後者は、呼び出す関数名。恥かしながら、今まで知りませ んでした。
人様のコードに面白いものを発見。
(when noninteractive (while command-line-args-left (disassemble-elc (car command-line-args-left)) (pop command-line-args-left)))
command-line-args-left is a variable defined in `startup.el'. Its value is nil Documentation: List of command-line args not yet processed.
bytecodeって、本当に速いの?
速いって言われているけど、どれぐらい速い? 余り比べたデータを見た事が ないので、自分でウジウジしてみる。
(defun xx(m) (let ((n (* m 1000 1000))) (dotimes (i n) (+ (+ i 0.1) (- i 0.1) (- i 0.2) (+ i 0.2))))) (defun ss(k) (let ((n (* k 1000)) (s "")) (dotimes (i n) (setq s (concat s "Hello ELISP"))))) (xx 1) ; (ss 4)
こんなコードを用意した。xxの方は、単に4個の数値を加算するだけ。4個の数 値は、ちょいと大きかったり、小さかったりするやつにした。ssの方はeditor を意識して、文字列の連結だ。
$ time emacs --batch -l zz.el # exec xx 1.53 real 1.49 user 0.03 sys $ time emacs --batch -l zz.el # exec ss 1.15 real 0.99 user 0.15 sys
M-x byte-compile-file RET zz.elして、zz.elcを作る。
$ time emacs --batch -l zz.elc # exec xx 1.07 real 1.04 user 0.03 sys $ time emacs --batch -l zz.elc # exec ss 0.28 real 0.25 user 0.03 sys
数値演算の方は大雑把に2/3ぐらいに短縮されたな。文字列結合は、大幅に速 くなった。編集には効果有りの判定で宜しいかな。
benchmark.el
上の実行例は、ちょっと馬鹿っぽい。emacs自身に何か支援機構が無いかと思っ て探してみた所、lisp/emacs-lispの中にツールを発見したよ。
M-x benchmark RET (xx 1)
Elapsed time: 1.756967s (0.756556s in 27 GCs)
M-x benchmark RET (ss 4)
Elapsed time: 1.759933s (1.660900s in 59 GCs)
emacsが起動中での計測なんで、実用的だな。尚、言うまでもない事だけど、 実行に先立ち、関数はロード若しくはC-x C-eで登録しておく事。
更に、benchmark.elを見ていたら、benchmark-run-compiled なんていうのを 発見。こいつは、インタラクティブに呼びだせないので、*scratch*で実験す る。
(benchmark-run-compiled (xx 1)) (1.821730136 27 0.7761346759999999) (1.819023088 27 0.7757935689999975)
何度実行しても、byte-compileの効果が表われない。で、じっと、コードを見 てて思いついた。実行される関数の方をコンパイルしておかねば効果なし 。
ああ、数値が並んでいるだけだけど、左から総実行時間、GCの回数、最後はGC に要した時間だ。
(byte-compile 'xx) #[(m) "^H\304_\211^Y\305^Z^[ ^KW\205$^@\306 \307\\ \310Z \311Z \312\\$\210 T\211^R\202 ^@+\207" [m n i --dotimes-limit-- 1000000 0 + 0.1 0.1 0.2 0.2] 7]
C-jして、コンパイル結果を晒してみた。(事前に、xxは登録してある事)
(benchmark-run (xx 1)) (1.3493899360000001 27 0.7853148949999973) (benchmark-run (ss 4)) (0.551152451 15 0.44608993599999636)
これで効果が確認できたな。スピードアップに寄与してるのは、GCの回数が減っ ているからだな。GC一回に約30ms。
GC時間を差し引いて計算すると、劇的に速くなるという事でもない。GCの回数 減少の寄与率が高い。そうよな、*.elだと、コードの実行でconsを大量に消費 するから、その回収に時間がかかって、遅くなるんだな。
次は、Lispのベンチと言うと必ずでてくるあれ。日本が誇る(特に官庁とか大 企業)たらい回しです。
(defun tarai (x y z) (if (<= x y) y (tarai (tarai (1- x) y z) (tarai (1- y) z x) (tarai (1- z) x y)))) (tarai 12 6 0)
実行してみる。
(benchmark-run (tarai 12 6 0)) (7.620963838 0 0.0) ;; el (4.919424396 0 0.0) ;; elc
taraiの秘話が、 ハッカーの遺言状──竹内郁雄の徒然苔 に出てた。 作者様と、skkの開発者の若い頃の尊影がありました。
compare with sbcl
折角sbclが入っているので、emacsと比較してみる。文字列の連結だけは、cl 用に書きかえたけど、他は一緒のコードだ。
CL-USER> (defun ss(k) (let ((n (* k 1000)) (s "")) (dotimes (i n) (setq s (concatenate 'string s "Hello ELISP"))))) SS CL-USER> (time (ss 4)) Evaluation took: 0.219 seconds of real time 0.215290 seconds of total run time (0.142418 user, 0.072872 system) [ Run times consist of 0.100 seconds GC time, and 0.116 seconds non-GC time.] 98.17% CPU 525,526,587 processor cycles 1 page fault 351,884,736 bytes consed CL-USER> (time (xx 1)) Evaluation took: 0.058 seconds of real time 0.059175 seconds of total run time (0.056022 user, 0.003153 system) 101.72% CPU 137,451,210 processor cycles 0 bytes consed CL-USER> (time (tarai 12 6 0)) Evaluation took: 0.626 seconds of real time 0.622920 seconds of total run time (0.608358 user, 0.014562 system) 99.52% CPU 1,502,459,331 processor cycles 0 bytes consed
tarai by python
taraiは Lisp用に開発されたんだけど、それをPythonで実行したらどうなる? lisp to pythonな変換器が有るんで、試してみる。hy用に若干修正。
(defn m1 [n] (- n 1)) (defn tarai [x y z] (if (<= x y) y (tarai (tarai (m1 x) y z) (tarai (m1 y) z x) (tarai (m1 z) x y)))) (tarai 12 6 0)
変換すると、python語が出きあがる。
[sakae@fb /tmp]$ ~/.local/bin/hyc tarai.hy Compiling tarai.hy [sakae@fb /tmp]$ time python3.6 __pycache__/tarai.cpython-36.pyc real 0m9.266s user 0m9.254s sys 0m0.008s
pythonは、度重なる関数呼出は苦手って事で宜しいでしょうかね。
Bug 踏んじゃった
とある日、FreeBSD 11.2でpkg upgradeしたら、ほとんどのpkgを新らしくする 様なはめになった。4半期毎のあれかな。それはそれでいいんだけど、
octave:1> help perl Unescaped left brace in regex is deprecated here (and will be fatal in Perl 5.32), passed through in regex; marked by <-- HERE in m/^\s+@([[:alnum:]][[:alnum:]\-]*)({ <-- HERE })?\s*/ at /usr/local/share/texinfo/Texinfo/Parser.pm line 5481. Unescaped left brace in regex is deprecated here (and will be fatal in Perl 5.32), passed through in regex; marked by <-- HERE in m/^\s+@([[:alnum:]][[:alnum:]\-]*)({ <-- HERE })?\s*(\@(c|comment)((\@|\s+).*)?)?/ at /usr/local/share/texinfo/Texinfo/Parser.pm line 5485. 'perl' is a function from the file /usr/local/share/octave/4.4.1/m/miscellaneous/perl.m
こんなエラーが出るようになった。オイラーの所だけ? ググったら、 print/texinfo Does not work well with perl 5.28 こんな報告が上っていた。オイラーの所だけじゃなくて、一安心。 手動でpatchを当てて一件落着。
それにしても、octaveからperlやpythonを呼び出せるなんて、知らなかったぞ。
そして裏でtexinfoが頻繁に呼び出されている事を知った。octaveのソースっ て、texinfoの情報やらサンプルスクリプトの混合隊になっているのね。Bugを 踏んだおかげで、知見が増えたと前向きに捉えておこう。