常见的Lisp SBCL循环性能调整

时间:2019-06-18 03:00:06

标签: common-lisp

我正在使用Euler项目问题​​5蛮力方法来测试性能调整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秒。

2 个答案:

答案 0 :(得分:7)

您可以使用build.gradle代替fixnum。后者包含所有整数,前者仅包含适合一个机器字的整数减去几个标记位(通常约为61或62位)。

integer循环中的声明位于正文的开头:

do

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

loop

代码改进:

  • 请不要在括号内留下指甲剪。

  • (loop :for m :of-type fixnum :upfrom 1 :when (has-all-divisors m 2 20) :do (return m)) 用于两个分支条件。

  • if有一个Loop关键字:

    always

答案 1 :(得分:4)

我不是CL方面的专家,但想提供一些建议,您可能会觉得有帮助。

常规样式

您不应在右括号的后面加上多余的一行。参见例如here中有关样式的一些评论。此外,文档字符串可以帮助其他人和您自己将来了解您的代码。

性能

我尚未审查自己的解决方案,但是我猜想指定fixnum代替integer会导致性能提高2,并且应该可以解决此问题。

循环

您可以使用has-all-divisors的{​​{1}}子句来写loop惯用语:

always

替代解决方案

如果我正确地记住了这个问题,则可以使用另一种算法,该算法应该更快一些。收集从2到20的整数的所有素数,并建立其乘积。