Recursion Mandelbrot集在Scheme中的实现非常缓慢

Recursion Mandelbrot集在Scheme中的实现非常缓慢,recursion,scheme,lisp,common-lisp,mandelbrot,Recursion,Scheme,Lisp,Common Lisp,Mandelbrot,我正在尝试学习Lisp/Scheme,并尝试在其中实现一个非常简单的mandelbrot集,以获得实践。我遇到的问题是代码运行非常非常慢。起初我认为这是因为我使用递归而不是命令式循环,但我尝试在python中重新编写或多或少相同的代码(包括递归)(它甚至没有尾部调用优化),并且运行非常平稳 因此,我想知道我的代码中是否有明显的遗漏,以及我能做些什么使它运行得更快 下面是Scheme(racket)中的代码片段。我在SBCL中也做了几乎相同的事情,速度相当 #lang racket (defin

我正在尝试学习Lisp/Scheme,并尝试在其中实现一个非常简单的mandelbrot集,以获得实践。我遇到的问题是代码运行非常非常慢。起初我认为这是因为我使用递归而不是命令式循环,但我尝试在python中重新编写或多或少相同的代码(包括递归)(它甚至没有尾部调用优化),并且运行非常平稳

因此,我想知道我的代码中是否有明显的遗漏,以及我能做些什么使它运行得更快

下面是Scheme(racket)中的代码片段。我在SBCL中也做了几乎相同的事情,速度相当

#lang racket

(define-syntax dotimes 
   (syntax-rules () 
     ((_ (var n res) . body) 
      (do ((limit n) 
           (var 0 (+ var 1))) 
          ((>= var limit) res) 
        . body)) 
     ((_ (var n) . body) 
      (do ((limit n) 
           (var 0 (+ var 1))) 
          ((>= var limit)) 
        . body))))

(define (print-brot zr zc)
  (if (< (+ (* zr zr) (* zc zc)) 2)
      (display "@")
      (display ".")))

(define (brot zr zc cr cc i)
  (if (= i 0)
      (print-brot zr zc)
      (let ((z2r (- (* zr zr) (* zc zc))) (z2c (* 2 zr zc)))
        (brot (+ z2r cr) (+ z2c cc) cr cc (- i 1)))))

(define (linspace i w)
  (/ (- i (/ w 2)) (/ w 4)))

(define (brot-grid w h n)
  (dotimes (i w)
           (dotimes (j h)
                    (let ((x (linspace i w)) (y (linspace j h)))
                      (brot 0 0 x y n)))
           (newline)))

(brot-grid 40 80 20)
#朗球拍
(定义语法点时间)
(语法规则()
((变量n res.正文)
(do)(限制n)
(变量0(+变量1)))
((>=风险限额)res)
(见正文)
((变量n.正文)
(do)(限制n)
(变量0(+变量1)))
((>=风险限额))
(正文)
(定义(打印brot zr zc)
(如果(<(+(*zr-zr)(*zc-zc))2)
(显示“@”)
(显示“))
(定义(brot zr zc cr cc i)
(如果(=i 0)
(打印brot zr zc)
(let((z2r(-zr-zr)(*zc-zc)))(z2c(*2-zr-zc)))
(brot(+z2r-cr)(+z2c-cc)cr-cc(-i 1()()))
(定义(linspace i w)
(/(-i(/w2))(/w4)))
(定义(brot栅格w h n)
(有时)
(时间(j h)
(让((x(linspace i w))(y(linspace j h)))
(brot 0 x y n)))
(新行)))
(布罗特格栅40 80 20)
(我希望代码块不是太杂乱,很难将其剥离为更简单的代码块)

另外,我知道Scheme和Common Lisp内置了复数,但我想用常规实数来测试它,我不认为这是它运行如此缓慢的原因

brot函数的参数“i”是迭代次数,brot grid的参数“n”也是每个点使用的迭代次数。当我将其设置为10以上时,代码将永远无法运行,这似乎不正常。所用时间的增加似乎也不是线性的,例如,在我的机器上,当n=10时只需要大约一秒钟,但当n=15时需要几分钟,甚至在n=20时都没有结束

那么,是什么让这段代码运行得这么慢呢


提前感谢

这里有一个常见的Lisp变体:

(defun print-brot (zr zc)
  (write-char (if (< (+ (* zr zr)
                        (* zc zc))
                     2.0d0)
                  #\@
                #\.)))

(defun brot (zr zc cr cc i)
  (loop repeat i
        for z2r = (- (* zr zr) (* zc zc))
        for z2c = (* 2.0d0 zr zc)
        until (or (> (abs zr) 1.0d50)
                  (> (abs zc) 1.0d50))
        do (setf zr (+ z2r cr)
                 zc (+ z2c cc)))
  (print-brot zr zc))

(defun linspace (i w)
  (/ (- i (/ w 2.0d0)) (/ w 4.0d0)))

(defun brot-grid (w h n)
  (terpri)
  (loop for i from 0.0d0 by 1.0d0
        repeat w do
        (loop for j from 0.0d0 by 1.0d0
              repeat h do
              (brot 0.0d0 0.0d0 (linspace i w) (linspace j h) n))
    (terpri)))
优化代码意味着:

  • 高级编译器优化设置
  • 可能会添加一些类型声明
  • 摆脱浮子

查看您的代码,我认为您正在使用有理数进行测试。这意味着相当精确的算术,缺点是你很快就会使用带有巨大bignum的有理数作为分子和分母

确保使用浮点(我建议使用双浮点)的一种方法是使用一个将所有输入转换为双浮点的中间函数,以便更轻松地键入(例如)
0
,而不是
0d0


一旦确定使用double可以加快速度,就可以开始在整个过程中添加类型声明,以使编译器能够为您生成更好的代码。

zr和zc应该非常大吗?我暂停了一下,zr有4000多个数字。因为Scheme有大整数,所以在所有程序内存被消耗之前,它不会抱怨大小?花车?如果您想使用实数/浮点数进行计算,那么您应该确保您实际使用了它们,并且您的操作也使用了它们。我看到很多整数和有理数运算,它们可能很慢,例如当使用bignums或big-rationals时。只需跟踪或步进函数,就可以看到函数使用的数字。超过10次迭代?Pshaw,想象一下1000!即使您可以实现非常快速的迭代方式,生成Mset也会很慢。如果你只是用基本的方式编写代码,它会非常慢。它是计算密集型的,所以您可能需要更好的语言选择。而且,您不需要复数函数,只需要非常快速的方法来处理定点整数或FPU实数。谢谢,问题似乎确实在于数字的表示。当Z的模大于2时,将其更改为停止迭代已经使其速度大大加快。确保使用正确的数字表示法应注意其余部分。我想我不习惯scheme根据你对数字的操作神奇地改变数字的表示方式,所以基本上只要把
linspace
中的常量
2
改成
2.0
就可以在十分之一秒内完成整个过程@艾蒂安·布莱那里有只虫子。它只对平方进行求和,而不求平方根,因此值应该是
4
,您可以将有理数限制在任何精度,例如小数点后100位。这比double要慢,但比精确的有理数要快得多。我认为Common Lisp除了精确的有理数外没有其他功能(除非作为实现扩展),所以如果你想要非精确的有理数,你可能必须自己实现。我的意思是,简单地乘以(10^n),向下截断为最接近的整数,并形成一个分母为10^n的新比率。这不是正确的做法,但也行,如果不行,再增加一点。(@意志力是可行的,但在这一点上,简单地使用双浮点数可能更容易(而且可能更精确地接近0.0d0…)嗯,在这个方案中提取尾数应该很容易,就像double那样。因此,100(或1000)个有意义的数字,然后:这当然是一个真正粗糙的“解决方案”(因为它无法控制累积误差)。有真正的自适应精度sche
*  (time (brot-grid 20 40 20))

........................................
....................@...................
....................@...................
....................@...................
...................@@@..................
.................@@@@@@@................
...................@@@..................
................@@@@@@@@@...............
..............@@@@@@@@@@@@@.............
............@@@@@@@@@@@@@@@@@...........
..............@@@@@@@@@@@@@.............
...............@@@@@@@@@@@..............
..................@...@.................
........................................
........................................
........................................
........................................
........................................
........................................
........................................
Evaluation took:
  0.003 seconds of real time
  0.002577 seconds of total run time (0.001763 user, 0.000814 system)
  100.00% CPU
  6,691,716 processor cycles
  2,064,384 bytes consed