Posted 2021-04-28 22:14:11 GMT
今日はSNSでEmacs Lispのネイティブコンパイラがメインラインに来たという話題で賑っていたので、早速GNU Emacsを--with-native-comp
でビルドして、Common Lispのネイテイブコンパイラと比較してどんなものなのか眺めてみました。
眺めるといっても、お馴染のfib
のマイクロベンチを走らせるだけですが。
まず、Emacs Lispで、通常の定義、バイトコンパイル、ネイティブコンパイルで比較してみます。
いまひとつ作法が分かっていないのですが、定義形式を合せるために、(setf symbol-function)
しています。
一応defun
での定義をファイルに書き出してからファイルをコンパイルする手順でも確認してみましたが、結果はほぼ同じようです。
最適化設定が良く分からないのですが、
(setq comp-speed 3)
するとCommon Lispでいう、(declaim (optimize speed 3))
的なことになるようなので、以下はこの設定の元で実験しています。
(setf (symbol-function 'fib)
(lambda (n)
(if (< n 2)
n
(+ (fib (1- n))
(fib (- n 2))))))(benchmark-call (lambda () (fib 30)) 1)
→ (0.540617072 0 0.0)
(benchmark-call (lambda () (fib 40)) 1)
→ (79.139097209 0 0.0)
(setf (symbol-function 'fib-bc)
(byte-compile
(lambda (n)
(if (< n 2)
n
(+ (fib-bc (1- n))
(fib-bc (- n 2)))))))(benchmark-call (lambda () (fib-bc 30)) 1)
→ (0.346994376 0 0.0)
(benchmark-call (lambda () (fib-bc 40)) 1)
→ (40.036792254 0 0.0)
(setf (symbol-function 'fib-nc)
(native-compile
(lambda (n)
(if (< n 2)
n
(+ (fib-nc (1- n))
(fib-nc (- n 2)))))))(benchmark-call (lambda () (fib-nc 30)) 1)
→ (0.253148658 0 0.0)
(benchmark-call (lambda () (fib-nc 40)) 1)
→ (29.594145706 0 0.0)
大体のところですが、
elisp: (fib 40) | Intel(R) Xeon(R) CPU E3-1230 v3 @ 3.30GHz |
---|---|
interp | 79sec |
byte-comp | 40sec |
native-comp | 29sec |
という結果でした。
大体のタイムは分かったので、似たようなスピードのCommon Lisp処理系ということだとCLISPあたりか、ということで、CLISPで関数をコンパイルした場合と、Emacs Lispのネイティブコンパイルのfibの結果を比較してみたところ、両者の結果は、ほぼ同じになりました。
なお、CLISPはネイティブコンパイラではなくバイトコンパイラの処理系です。
(setf (symbol-function 'fib)
(lambda (n)
(if (< n 2)
n
(+ (fib (1- n))
(fib (- n 2))))))(time (fib 30))
Real time: 0.248056 sec.
Run time: 0.25 sec.
Space: 0 Bytes
→ 832040
(time (fib 40))
Real time: 30.663303 sec.
Run time: 30.59 sec.
Space: 0 Bytes
→ 102334155
CLISP: (fib 40) | Intel(R) Xeon(R) CPU E3-1230 v3 @ 3.30GHz |
---|---|
interp | 116sec |
byte-comp | 30sec |
Common Lispのメジャーな処理系は大体ネイティブコンパイラですが、LispWorksで最適化設定なしの場合は、こんな感じです。
(setf (symbol-function 'fib)
(lambda (n)
(if (< n 2)
n
(+ (fib (1- n))
(fib (- n 2))))))(compile 'fib)
Timing the evaluation of (fib 40)
User time = 1.350
System time = 0.010
Elapsed time = 1.293
Allocation = 269016 bytes
0 Page faults
→ 102334155
LispWorks: (fib 40) | Intel(R) Xeon(R) CPU E3-1230 v3 @ 3.30GHz |
---|---|
interp | 186sec |
native-comp | 1.35sec |
Emacs Lispのネイティブコンパイルfibと比べると大体22倍程度の速さです。
ちなみにLispWorksのインタプリタ実行はかなり遅いことがわかりますが、Common Lisp仕様がコンパイル指向なので、インタプリタはおまけだったり、サポートしていなかったりの処理系が殆どです。
なお、SBCLやLispWorksで型宣言や再帰的なインライン展開を実施して最速を狙うと、ネイティブコンパイルのEmacs Lispの約95倍程度の速度が出ます。
関数呼出しの速度を計測するためのfib
のようなマイクロベンチでインライン展開するのは卑怯な気もするのですが、gcc等も展開してくるので、gccに合せるならアリかなと思います。
(defun fib (n)
(declare (optimize (speed 3) (safety 0) (debug 0) (compilation-speed 0))
(fixnum n))
(labels ((fib (n)
(declare (fixnum n))
(the fixnum
(if (< n 2)
n
(+ (fib (1- n))
(fib (- n 2)))))))
(declare (inline fib))
(fib n)))CL-USER> (time (fib 40))
Evaluation took:
0.322 seconds of real time
0.320000 seconds of total run time (0.320000 user, 0.000000 system)
99.38% CPU
1,061,775,900 processor cycles
0 bytes consed
いまのところEmacs Lispの最適化の作法があまり確立していないようなのですが、Common Lisp風に人が指示する手段もそこそこ用意されているようです。
ただ、まだ発展途上のようでCommon Lispの感覚でコンパイラに最適化のヒント与えようとしても上手く行かない様子。
例えば、comp-hint-fixnum
は、(the fixnum ...)
な雰囲気のもののようですが、(optimize (speed 3))
で使うと現状では却って遅くなったりします。
また、Common Lispではdisassemble
で結果を確認しつつ追い込んでいくのが普通ですが、Common Lispのネイティブコンパイラのディスアセンブルの結果と違ってCのコンパイラの世界と行き来している感があります。
Emacs Lispが今後Common Lispのように人力チューニングを中心に展開していくのか、JITを中心にしていくのかは分かりませんが、Common Lisp風なシステムを目指すのであれば、もう少し道具が充実する必要がありそうです。
今回のfib
のdisassemble
の結果は下記のようになります。
なお、現状、AT&T表記決め打ちのようなのですが、disassemble-internal
の中でobjdump
を呼んでいるだけのようなので、ここをいじれば好きな表記に変更できるようです。
disassemble-internal:
...
(call-process "objdump" nil (current-buffer) t "-S" "-M" "intel"
(native-comp-unit-file (subr-native-comp-unit obj)))
...
0000000000001290 <F6669622d6e63_fib_nc_0>:
1290: 41 54 push r12
1292: 55 push rbp
1293: 53 push rbx
1294: 48 83 ec 50 sub rsp,0x50
1298: 4c 8b 25 49 2d 00 00 mov r12,QWORD PTR [rip+0x2d49] # 3fe8 <freloc_link_table@@Base-0x5f8>
129f: 48 8b 2d 3a 2d 00 00 mov rbp,QWORD PTR [rip+0x2d3a] # 3fe0 <d_reloc@@Base-0x580>
12a6: 49 8b 1c 24 mov rbx,QWORD PTR [r12]
12aa: 48 8b 7d 00 mov rdi,QWORD PTR [rbp+0x0]
12ae: ff 93 a8 26 00 00 call QWORD PTR [rbx+0x26a8]
12b4: bf 02 00 00 00 mov edi,0x2
12b9: 48 89 e6 mov rsi,rsp
12bc: 48 c7 44 24 08 0a 00 mov QWORD PTR [rsp+0x8],0xa
12c3: 00 00
12c5: 48 89 04 24 mov QWORD PTR [rsp],rax
12c9: ff 93 28 26 00 00 call QWORD PTR [rbx+0x2628]
12cf: 48 8b 7d 00 mov rdi,QWORD PTR [rbp+0x0]
12d3: 48 85 c0 test rax,rax
12d6: 74 18 je 12f0 <F6669622d6e63_fib_nc_0+0x60>
12d8: ff 93 a8 26 00 00 call QWORD PTR [rbx+0x26a8]
12de: 48 83 c4 50 add rsp,0x50
12e2: 5b pop rbx
12e3: 5d pop rbp
12e4: 41 5c pop r12
12e6: c3 ret
12e7: 66 0f 1f 84 00 00 00 nop WORD PTR [rax+rax*1+0x0]
12ee: 00 00
12f0: ff 93 a8 26 00 00 call QWORD PTR [rbx+0x26a8]
12f6: 48 89 c7 mov rdi,rax
12f9: 8d 40 fe lea eax,[rax-0x2]
12fc: a8 03 test al,0x3
12fe: 75 20 jne 1320 <F6669622d6e63_fib_nc_0+0x90>
1300: 48 ba 00 00 00 00 00 movabs rdx,0xe000000000000000
1307: 00 00 e0
130a: 48 89 f8 mov rax,rdi
130d: 48 c1 f8 02 sar rax,0x2
1311: 48 39 d0 cmp rax,rdx
1314: 74 0a je 1320 <F6669622d6e63_fib_nc_0+0x90>
1316: 48 8d 04 85 fe ff ff lea rax,[rax*4-0x2]
131d: ff
131e: eb 0a jmp 132a <F6669622d6e63_fib_nc_0+0x9a>
1320: 49 8b 04 24 mov rax,QWORD PTR [r12]
1324: ff 90 90 25 00 00 call QWORD PTR [rax+0x2590]
132a: 48 8b 55 18 mov rdx,QWORD PTR [rbp+0x18]
132e: 48 8d 74 24 10 lea rsi,[rsp+0x10]
1333: 48 89 44 24 18 mov QWORD PTR [rsp+0x18],rax
1338: bf 02 00 00 00 mov edi,0x2
133d: 48 89 54 24 10 mov QWORD PTR [rsp+0x10],rdx
1342: ff 93 e0 1a 00 00 call QWORD PTR [rbx+0x1ae0]
1348: 48 8b 7d 00 mov rdi,QWORD PTR [rbp+0x0]
134c: 49 89 c4 mov r12,rax
134f: ff 93 a8 26 00 00 call QWORD PTR [rbx+0x26a8]
1355: 48 8d 74 24 20 lea rsi,[rsp+0x20]
135a: bf 02 00 00 00 mov edi,0x2
135f: 48 c7 44 24 28 0a 00 mov QWORD PTR [rsp+0x28],0xa
1366: 00 00
1368: 48 89 44 24 20 mov QWORD PTR [rsp+0x20],rax
136d: ff 93 f8 25 00 00 call QWORD PTR [rbx+0x25f8]
1373: 48 8b 55 18 mov rdx,QWORD PTR [rbp+0x18]
1377: 48 8d 74 24 30 lea rsi,[rsp+0x30]
137c: bf 02 00 00 00 mov edi,0x2
1381: 48 89 44 24 38 mov QWORD PTR [rsp+0x38],rax
1386: 48 89 54 24 30 mov QWORD PTR [rsp+0x30],rdx
138b: ff 93 e0 1a 00 00 call QWORD PTR [rbx+0x1ae0]
1391: 66 49 0f 6e c4 movq xmm0,r12
1396: 48 8d 74 24 40 lea rsi,[rsp+0x40]
139b: bf 02 00 00 00 mov edi,0x2
13a0: 66 48 0f 6e c8 movq xmm1,rax
13a5: 66 0f 6c c1 punpcklqdq xmm0,xmm1
13a9: 0f 29 44 24 40 movaps XMMWORD PTR [rsp+0x40],xmm0
13ae: ff 93 00 26 00 00 call QWORD PTR [rbx+0x2600]
13b4: 48 83 c4 50 add rsp,0x50
13b8: 5b pop rbx
13b9: 5d pop rbp
13ba: 41 5c pop r12
13bc: c3 ret
13bd: 0f 1f 00 nop DWORD PTR [rax]
■
HTML generated by 3bmd in LispWorks 7.0.0