*lisp and bytecode

去年のお菓子をみている。

NetBSD上に最小限のデスクトップ環境を用意する+VNCでアクセスできるようにする

懐しいな。こういう設定を昔はよくやったものだ。

重力プログラミング入門「第2回:Pythonで重力波を解析する」

Python強いな。みんなで使えば怖くない。

Python 環境構築いろいろ

最近のインストール事情らしい。まあ、pythonは使わないから、どうでもいい 事だけどね。

hylang/hy

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!

dis --- Python バイトコードの逆アセンブラ

pycを読む(スクリプト味)

さすが、みんなのPythonだけあるな。ドキュメントがしっかり有って、関係資 料も公開されてるな。

clojure

ClojureScript の周辺環境を理解するまで

clojure-emacs/cider

cider

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))

いまから始めるCommon Lisp

逆引きCommon Lisp

今こそ Lisp 入門 - Lisp の調べ

以上、FreeBSDにたまたま入れた、octaveから派生したLispな環境でした。

bytecode

emacs 25.1.1の環境でskkを起動すると

skk-setup-modeline: Invalid byte opcode: op=183, ptr=5

こんなエラーが出てる。普段は emacs 26を使っているんで、bytecodeに齟齬 が生じているんだな。emacsの困ったちゃんであります。

旨い事に下記のような記事が出ていたので、楽しんでみる事にする。

Emacs Byte-code Internals

バイトコードインタプリタを使ってバイトコードの解説

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を 踏んだおかげで、知見が増えたと前向きに捉えておこう。