Common lisp 常见Lisp SBCL循环性能调整

Common lisp 常见Lisp SBCL循环性能调整,common-lisp,Common Lisp,我正在使用ProjectEulerProblem5BruteForce方法测试性能调整CL代码。我对这门语言不熟悉,想知道如何做这些事情。我编写了一个C实现,运行时间约为1.3秒。我最初的天真CL实现大约需要15秒钟 这是我的初始CL代码 (defpackage :problem-5 (:use #:cl) (:export :divides-even-p :has-all-divisors :smallest-multiple) ) (

我正在使用ProjectEulerProblem5BruteForce方法测试性能调整CL代码。我对这门语言不熟悉,想知道如何做这些事情。我编写了一个C实现,运行时间约为1.3秒。我最初的天真CL实现大约需要15秒钟

这是我的初始CL代码

(defpackage :problem-5
  (:use #:cl)
  (:export :divides-even-p
           :has-all-divisors
           :smallest-multiple)
  )

(in-package :problem-5)

(defun divides-even-p (num divisor)
  (= 0 (rem num divisor))
  )

(defun has-all-divisors (num start stop)
  (loop for divisor from start to stop do
       (cond
         ((not (divides-even-p num divisor)) (return-from has-all-divisors nil))
         )
       (+ divisor 1)
       )
  t
  )

(defun smallest-multiple (n)
  (do ((multiple 1 (+ multiple 1)))
      ((has-all-divisors multiple 1 n) (format t "~A" multiple))
    ))

(time (smallest-multiple 20))
到目前为止,我读到的技巧是1)优化速度和安全性,2)内联函数和3)显式设置变量类型。应用这些东西,我得到以下代码

(defpackage :problem-5
  (:use #:cl)
  (:export :divides-even-p
           :has-all-divisors
           :smallest-multiple)
  )

(in-package :problem-5)

(declaim (optimize (speed 3) (safety 0))
         (inline divides-even-p)
         (inline has-all-divisors)
         )

(defun divides-even-p (num divisor)
  (declare (type integer num divisor))

  (zerop (rem num divisor))
  )

(defun has-all-divisors (num start stop)
  (declare (type integer num start stop))

  (loop for divisor of-type integer from start to stop do
       (cond
         ((not (divides-even-p num divisor)) (return-from has-all-divisors nil))
         )
       )
  t
  )

(defun smallest-multiple ()
  (do ((multiple 1 (+ multiple 1)))
      ((has-all-divisors multiple 2 20) (format t "~A" multiple))
    ))

(time (smallest-multiple))

现在,当我运行这个版本时,它只需7秒而不是15秒。2倍加速,所以方向正确。还可以做些什么来加速它?对我来说最引人注目的是最小倍数的do循环。首先,我不知道如何为多变量指定类型。你是怎么做到的?有没有更好的方法来进行开放式循环,从而产生更少的代码?从这里开始,您将如何努力提高性能?C代码的运行时间约为1.3秒,因此我很乐意将其缩短到2秒或3秒。

我不是CL方面的专家,但我想给出一些建议,您可能会觉得这些建议很有用

一般风格

不应在额外的一行中留下右括号。有关风格的一些备注,请参见。此外,文档字符串将有助于其他人和您将来理解您的代码

性能

我还没有回顾我自己对这个问题的解决方案,但我想指定
fixnum
而不是
integer
将导致性能上的另一个因子为2,应该可以解决这个问题

循环

您可以使用
循环
s
始终
子句编写具有所有除数的
语句:

(defun has-all-divisors (num start stop)
  (declare (type fixnum num start stop))
  (loop for divisor of-type fixnum from start to stop
        always (divides-even-p num divisor)))
替代解决方案


如果我没记错这个问题,你可以使用另一种算法,它应该快得多。收集2到20之间整数的所有素数因子并生成它们的乘积。

对于其中一个,您可以使用
fixnum
而不是
integer
。后者包含所有整数,前者只包含适合机器字减去几个标记位(通常是61或62位左右)的整数

do
循环中的声明出现在正文的开头:

(do ((m 1 (1+ m)))
    ((has-all-divisors m 2 20)
     m)
  (declare (fixnum m)))
您也可以在此处使用
循环

(loop :for m :of-type fixnum :upfrom 1
      :when (has-all-divisors m 2 20)
        :do (return m))
代码改进:

  • 请不要像剪指甲一样把括号放在周围

  • 如果
用于双分支条件,则使用

  • 循环
    有一个
    始终
    关键字:

    (loop :for divisor :of-type fixnum :from start :to stop
          :always (divides-even-p num divisor))
    

  • 使用暴力与避免不必要的工作并不矛盾。您希望有一个数字是所有值的倍数,从2到20,特别是20。因此,从20开始,逐步到20(这对C也适用)。