CLOS make-instance非常慢,导致SBCL中的堆耗尽

时间:2014-11-24 00:04:56

标签: performance lisp common-lisp sbcl clos

我正在使用Common Lisp(64位Debian GNU / Linux中的SBCL 1.1.5)编写多体系结构汇编程序/反汇编程序,目前汇编程序为x86-64的子集生成正确的代码。为了组装x86-64汇编代码,我使用一个哈希表,其中汇编指令助记符(字符串),如"jc-rel8""stosb"是返回一个或多个编码函数列表的键,如下所示:

(defparameter *emit-function-hash-table-x64* (make-hash-table :test 'equalp))
(setf (gethash "jc-rel8" *emit-function-hash-table-x64*) (list #'jc-rel8-x86))
(setf (gethash "stosb"   *emit-function-hash-table-x64*) (list #'stosb-x86))

编码函数就像这些(有些更复杂):

(defun jc-rel8-x86 (arg1 &rest args)
  (jcc-x64 #x72 arg1))

(defun stosb-x86 (&rest args)
  (list #xaa))

现在我试图通过使用转换为Common Lisp CLOS语法的NASM's (NASM 2.11.06)指令编码数据(文件insns.dat)来合并完整的x86-64指令集。这意味着用自定义x86-asm-instruction类的实例替换用于发出二进制代码的常规函数​​(如上面的函数)(到目前为止,这是一个非常基本的类,带有:initarg的大约20个插槽,{{1} },:reader等),其中带有参数的:initform方法将用于发出给定指令(助记符)和参数的二进制代码。转换后的指令数据看起来像这样(但它超过40'000行,正好是7193 emit和7193 make-instance'。


;; first mnemonic + operand combination instances (:is-variant t).
;; there are 4928 such instances for x86-64 generated from NASM's insns.dat.

(eval-when (:compile-toplevel :load-toplevel :execute)

(setf Jcc-imm-near (make-instance 'x86-asm-instruction
:name "Jcc"
:operands "imm|near"
:code-string "[i: odf 0f 80+c rel]"
:arch-flags (list "386" "BND")
:is-variant t))

(setf STOSB-void (make-instance 'x86-asm-instruction
:name "STOSB"
:operands "void"
:code-string "[ aa]"
:arch-flags (list "8086")
:is-variant t))

;; then, container instances which contain (or could be refer to instead)
;; the possible variants of each instruction.
;; there are 2265 such instances for x86-64 generated from NASM's insns.dat.

(setf Jcc (make-instance 'x86-asm-instruction
                         :name "Jcc"
                         :is-container t
                         :variants (list Jcc-imm-near
                                         Jcc-imm64-near
                                         Jcc-imm-short
                                         Jcc-imm
                                         Jcc-imm
                                         Jcc-imm
                                         Jcc-imm)))

(setf STOSB (make-instance 'x86-asm-instruction
                           :name "STOSB"
                           :is-container t
                           :variants (list STOSB-void)))

;; thousands of objects more here...

) ; this bracket closes (eval-when (:compile-toplevel :load-toplevel :execute)

我已经使用一个简单的Perl脚本将NASM的setf转换为Common Lisp语法(如上所述)(下面进一步,但对脚本本身没有任何兴趣)并且原则上它可以工作。所以它可以工作,但编译这些7193对象真的很慢,通常会导致堆耗尽。在具有16G内存的Linux Core i7-2760QM笔记本电脑上,编译insns.dat代码块与7193个对象(如上所述)需要7分钟以上,有时会导致堆耗尽,如下所示:

;; Swank started at port: 4005.
* Heap exhausted during garbage collection: 0 bytes available, 32 requested.
 Gen StaPg UbSta LaSta LUbSt Boxed Unboxed LB   LUB  !move  Alloc  Waste   Trig    WP  GCs Mem-age
   0:     0     0     0     0     0     0     0     0     0        0     0 41943040    0   0  0.0000
   1:     0     0     0     0     0     0     0     0     0        0     0 41943040    0   0  0.0000
   2:     0     0     0     0     0     0     0     0     0        0     0 41943040    0   0  0.0000
   3: 38805 38652     0     0 49474 15433   389   416     0 2144219760 9031056 1442579856    0   1  1.5255
   4: 127998 127996     0     0 45870 14828   106   143   199 1971682720 25428576  2000000    0   0  0.0000
   5:     0     0     0     0     0     0     0     0     0        0     0  2000000    0   0  0.0000
   6:     0     0     0     0  1178   163     0     0     0 43941888     0  2000000  985   0  0.0000
   Total bytes allocated    = 4159844368
   Dynamic-space-size bytes = 4194304000
GC control variables:
   *GC-INHIBIT* = true
   *GC-PENDING* = in progress
   *STOP-FOR-GC-PENDING* = false
fatal error encountered in SBCL pid 9994(tid 46912556431104):
Heap exhausted, game over.

Welcome to LDB, a low-level debugger for the Lisp runtime environment.
ldb>

我必须为SBCL添加(eval-when (:compile-toplevel :load-toplevel :execute)参数才能完全编译,但是在分配4千兆字节的动态空间后,堆仍然会耗尽。即使堆耗尽了,只需在类中添加一个槽(用于这些实例的--dynamic-space-size 4000类)之后编译7193个实例超过7分钟对于REPL中的交互式开发来说太过分了(我使用{ {3}},如果重要的话。)

这是'x86-asm-instruction输出:

;   caught 18636 WARNING conditions


; insns.fasl written
; compilation finished in 0:07:11.329
Evaluation took:
  431.329 seconds of real time
  238.317000 seconds of total run time (234.972000 user, 3.345000 system)
  [ Run times consist of 6.073 seconds GC time, and 232.244 seconds non-GC time. ]
  55.25% CPU
  50,367 forms interpreted
  784,044 lambdas converted
  1,031,842,900,608 processor cycles
  19,402,921,376 bytes consed

使用OOP(CLOS)可以合并指令助记符(例如上面的(time (compile-filejcstosb),指令的允许操作数(:name),指令的二进制编码(例如:operands的{​​{1}},#xaa)和一个对象中指令的可能架构限制(stosb)。但似乎至少我3岁的计算机效率不足以快速编译大约7000个CLOS对象实例。

我的问题是:是否有某种方法可以让SBCL的:code-string更快,或者我应该在常规函数中保存汇编代码,如上面的示例所示?我也很高兴知道任何其他可能的解决方案。

以下是Perl脚本,以防万一:

:arch-flags

1 个答案:

答案 0 :(得分:10)

18636警告看起来非常糟糕,首先要删除所有警告。

我首先要摆脱EVAL-WHEN围绕这一切。对我来说没有多大意义。直接加载文件,或编译并加载文件。

另请注意,当变量未定义时,SBCL不喜欢(setf STOSB-void ...)DEFVARDEFPARAMETER引入了新的顶级变量。 SETF只是设置它们,但不定义它们。这应该有助于摆脱警告。

同样:is-container t:is-variant t气味,这些属性应该转换为继承的类(例如作为mixin)。容器有变种。变体没有变体。