Mandelbrot在Scheme中设置的实现非常缓慢

时间:2015-08-10 16:42:08

标签: recursion scheme lisp common-lisp mandelbrot

我正在尝试学习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)

(我希望代码块不会过于集中,很难将其剥离成更简单的东西)

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

brot函数的参数“i”是迭代次数,brot-grid的参数“n”也是每个点使用的迭代次数。当我将它设置为大于10时,代码将永远运行,这似乎不正常。所用时间的增加似乎也不是线性的,例如我的机器只需要大约一秒钟,n = 10但n = 15需要几分钟,而n = 20

那么,是什么让这段代码运行得如此之慢?

提前致谢

2 个答案:

答案 0 :(得分:4)

查看您的代码,我认为您正在使用有理数来测试它。这意味着非常准确的算术,缺点是你最终会使用巨大的bignums作为分子和分母的理性。

确保你使用浮点数的一种方法(我建议使用双浮点数)是有一个中间函数将所有输入转换为双精度数,以便更容易只输入(比如说)0 0d0

一旦你确定使用双精度使其更快,你就可以开始在整个过程中使用类型声明,使编译器能够为你生​​成更好的代码。

答案 1 :(得分:2)

这是一个Common 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)))

注意使用double float常量。同时迭代双浮点数和整数。

在SBCL中运行它未优化,但编译为本机代码:

*  (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

然后优化代码意味着:

  • 更高的编译器优化设置
  • 可能添加一些类型声明
  • 摆脱浮出水面