【问题标题】:Common Lisp - Consing Optimization question - loops and &rest parametersCommon Lisp - Consing 优化问题 - 循环和 &rest 参数
【发布时间】:2020-06-14 23:34:54
【问题描述】:

我已经使用 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 出现如此多的问题?
  • 是什么导致了这么多类型的consing?
    • 是循环吗?
    • 参数&rest args 是否导致consing - 从left right 构建列表args?
      • 我总是只传递类型旁边的 2 个参数,例如 check-number-operands 中的类型
      • 定义一个只以left right 而不是&rest args 作为参数的type? 函数是否有性能优势?
    • 我知道我可以用宏替换 type?,但我很困惑为什么程序中最不复杂的操作之一有这么大的权重。

谢谢你:)

【问题讨论】:

  • type? 中,将vars 声明为dynamic-extent 可能会解决其内存问题。只需将(declare (dynamic-extent vars)) 放在文档字符串下方即可。这允许编译器堆栈分配变量并在函数返回时自动释放它,而无需涉及垃圾收集器。

标签: optimization common-lisp


【解决方案1】:

我从您的分析器输出中假设您正在使用 SBCL。

type?引起的consing几乎肯定是&rest参数的结果。如果有的话,循环不应该足以解释您的分析结果。我进行了一些测试,发现包含两个参数的 &rest 参数的内存使用情况相似。您可以按照我在评论中提到的那样堆栈分配 vars 参数,也可以按照您提到的那样重写函数以恰好采用三个参数。

lox-callable-arity 中的 consing 可能是由于函数在返回对象时复制了对象的 arity 槽(我的测试似乎再次支持这个理论)。我认为当您手动定义读取器函数时,它不会获得 SBCL 应用于使用 defclass 插槽选项定义的读取器和访问器的一些优化,例如内联。您可能应该删除该定义并将您的 arity 插槽定义更改为 (arity :type integer :reader lox-callable-arity) 以更清晰地获得该插槽的优化读取器功能,或者将 (declaim (inline lox-callable-arity)) 放在方法定义之上。我的猜测是您的 defclass++ 宏使用 defmethod 来定义类的默认访问器。也应该改为使用槽选项访问器或内联函数,以避免将来出现类似问题。

【讨论】:

  • 谢谢 :) 实际上 defclass++ 扩展为一个带有 :accessor 和 :initarg 的 defclass 以及一个在设置插槽时检查类型的元类。
猜你喜欢
  • 2014-06-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-07-17
  • 1970-01-01
  • 2018-09-23
  • 2013-05-16
  • 1970-01-01
相关资源
最近更新 更多