Common Lisp - Consing 优化问题 - 循环和 &rest 参数
Common Lisp - Consing Optimization question - loops and &rest parameters
我已经使用 Common Lisp 完成了一种语言的实现,我正在寻求优化它,因为使用 Lisp 需要大约 1400 秒而不是 Java 中的大约 72 秒。 (此处代码 cl-lox)。
我启动了探查器并得到了这个头号罪魁祸首:
seconds | gc | consed | calls | sec/call | name
------------------------------------------------------------------
32.879 | 0.000 | 0 | 104,512,464 | 0.000000 | LOX.INTERPRETER::LOOKUP-VARIABLE
6.395 | 0.062 | 1,162,823,904 | 29,860,705 | 0.000000 | LOX.CALLABLE:LOX-CALLABLE-ARITY
6.314 | 0.139 | 2,442,330,208 | 74,651,757 | 0.000000 | LOX.INTERPRETER::TYPE?
5.220 | 0.000 | 0 | 59,721,406 | 0.000000 | LOX.INTERPRETER::CHECK-NUMBER-OPERANDS
2.395 | 0.000 | 0 | 29,860,703 | 0.000000 | LOX.INTERPRETER::EVAL-TRUTHY-P
0.062 | 0.000 | 0 | 29,860,703 | 0.000000 | LOX.INTERPRETER::TRUTHY-P
0.001 | 0.000 | 65,520 | 35 | 0.000019 | LOX.RESOLVER:RESOLVE
下面是一些罪魁祸首:
;;; Related to lox-callable-arity:
;; defclass++ is a macro on top of defclass to add accessors and a default constructor
(defclass++ lox-native-function (lox-callable)
((name :type string)
(arity :type integer)
(fn :type function)
(str-repr :type string)))
(defmethod lox-callable-arity ((callee lox-native-function))
(slot-value callee 'arity))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; Related to type? and check-number-operands
(defun type? (type-specifier &rest vars)
"Ensure all vars are of type type-specifier."
(loop for var in vars always (typep var type-specifier)))
(defun* check-number-operands ((operator token:token) left right)
(when (not (type? 'number left right))
(error 'lox.error:lox-runtime-error
:token operator
:message (format nil "Operands of '~A' must be numbers." @operator.lexeme))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
问题:
- 是什么导致了对 lox-callable-arity 如此多的关注?
- 是什么导致了这么多类型的问题?
- 是循环吗?
- 参数
&rest args
是否会导致 consing - 从 left right
构建列表参数?
- 除了检查数字操作数中的类型,我总是只传递 2 个参数
- 定义一个只接受
left right
而不是 &rest args
作为参数的 type?
函数是否有性能优势?
- 我知道我可以用宏替换
type?
,但我很困惑为什么程序中最简单的操作之一有如此大的权重。
谢谢:)
根据您的分析器输出,我假设您正在使用 SBCL。
由type?
引起的coning几乎肯定是&rest
参数的结果。如果有的话,循环应该不足以解释您的分析结果。我 运行 进行了一些测试,发现包含两个参数的 &rest
参数的内存使用情况相似。您可以按照我在评论中提到的那样堆栈分配 vars
参数,或者重写函数以恰好采用您提到的三个参数。
lox-callable-arity
中的 consing 可能是由函数在返回对象时复制对象的 arity
槽引起的(我的测试似乎再次支持这个理论)。我认为当您手动定义 reader 函数时,它不会获得 SBCL 应用于 readers 和使用 defclass
插槽选项定义的访问器的一些优化,例如内联。您可能应该删除该定义并将 arity
插槽定义更改为 (arity :type integer :reader lox-callable-arity)
以更干净地为插槽获得优化的 reader 函数,或者将 (declaim (inline lox-callable-arity))
放在方法定义之上。我的猜测是您的 defclass++
宏使用 defmethod
来定义 class 的默认访问器。也应该改为使用插槽选项访问器或内联函数,以避免将来出现类似问题。
我已经使用 Common Lisp 完成了一种语言的实现,我正在寻求优化它,因为使用 Lisp 需要大约 1400 秒而不是 Java 中的大约 72 秒。 (此处代码 cl-lox)。
我启动了探查器并得到了这个头号罪魁祸首:
seconds | gc | consed | calls | sec/call | name
------------------------------------------------------------------
32.879 | 0.000 | 0 | 104,512,464 | 0.000000 | LOX.INTERPRETER::LOOKUP-VARIABLE
6.395 | 0.062 | 1,162,823,904 | 29,860,705 | 0.000000 | LOX.CALLABLE:LOX-CALLABLE-ARITY
6.314 | 0.139 | 2,442,330,208 | 74,651,757 | 0.000000 | LOX.INTERPRETER::TYPE?
5.220 | 0.000 | 0 | 59,721,406 | 0.000000 | LOX.INTERPRETER::CHECK-NUMBER-OPERANDS
2.395 | 0.000 | 0 | 29,860,703 | 0.000000 | LOX.INTERPRETER::EVAL-TRUTHY-P
0.062 | 0.000 | 0 | 29,860,703 | 0.000000 | LOX.INTERPRETER::TRUTHY-P
0.001 | 0.000 | 65,520 | 35 | 0.000019 | LOX.RESOLVER:RESOLVE
下面是一些罪魁祸首:
;;; Related to lox-callable-arity:
;; defclass++ is a macro on top of defclass to add accessors and a default constructor
(defclass++ lox-native-function (lox-callable)
((name :type string)
(arity :type integer)
(fn :type function)
(str-repr :type string)))
(defmethod lox-callable-arity ((callee lox-native-function))
(slot-value callee 'arity))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; Related to type? and check-number-operands
(defun type? (type-specifier &rest vars)
"Ensure all vars are of type type-specifier."
(loop for var in vars always (typep var type-specifier)))
(defun* check-number-operands ((operator token:token) left right)
(when (not (type? 'number left right))
(error 'lox.error:lox-runtime-error
:token operator
:message (format nil "Operands of '~A' must be numbers." @operator.lexeme))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
问题:
- 是什么导致了对 lox-callable-arity 如此多的关注?
- 是什么导致了这么多类型的问题?
- 是循环吗?
- 参数
&rest args
是否会导致 consing - 从left right
构建列表参数?- 除了检查数字操作数中的类型,我总是只传递 2 个参数
- 定义一个只接受
left right
而不是&rest args
作为参数的type?
函数是否有性能优势?
- 我知道我可以用宏替换
type?
,但我很困惑为什么程序中最简单的操作之一有如此大的权重。
谢谢:)
根据您的分析器输出,我假设您正在使用 SBCL。
由type?
引起的coning几乎肯定是&rest
参数的结果。如果有的话,循环应该不足以解释您的分析结果。我 运行 进行了一些测试,发现包含两个参数的 &rest
参数的内存使用情况相似。您可以按照我在评论中提到的那样堆栈分配 vars
参数,或者重写函数以恰好采用您提到的三个参数。
lox-callable-arity
中的 consing 可能是由函数在返回对象时复制对象的 arity
槽引起的(我的测试似乎再次支持这个理论)。我认为当您手动定义 reader 函数时,它不会获得 SBCL 应用于 readers 和使用 defclass
插槽选项定义的访问器的一些优化,例如内联。您可能应该删除该定义并将 arity
插槽定义更改为 (arity :type integer :reader lox-callable-arity)
以更干净地为插槽获得优化的 reader 函数,或者将 (declaim (inline lox-callable-arity))
放在方法定义之上。我的猜测是您的 defclass++
宏使用 defmethod
来定义 class 的默认访问器。也应该改为使用插槽选项访问器或内联函数,以避免将来出现类似问题。