【发布时间】:2015-11-02 16:22:52
【问题描述】:
我正在尝试学习 Lisp/Scheme,并尝试在其中实现一个非常简单的 mandelbrot 版本以进行练习。我遇到的问题是代码运行非常非常慢。起初我以为是因为我使用递归而不是命令式循环,但我尝试在 python 中重写或多或少相同的代码(包括递归)(甚至没有尾调用优化),它运行很流畅
所以我想知道我的代码中是否缺少一些明显的东西,以及我可以做些什么来让它运行得更快。
这是 Scheme (racket) 中的代码 sn-p。我在 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 时都没有完成
那么,是什么让这段代码运行如此缓慢?
提前致谢
【问题讨论】:
-
zr和zc应该很大吗?我暂停了一下,zr有超过 4000 个数字。由于 Scheme 有 bigintegers,它不会抱怨大小,直到所有程序内存都被消耗完。 -
'实数'?浮动?如果您想使用实数/浮点数进行计算,那么您应该确保您实际使用它们并且您的操作也使用它们。我看到很多整数和有理运算,它们可能会很慢,例如在使用大数或大有理数时。只需跟踪或步进函数,您就会看到函数使用的数字。
-
超过 10 次迭代? Pshaw,想象一下1000!即使您可以实现非常快速的迭代方式,生成 Mset 也会很慢。如果您只是以基本方式编写代码,它会非常慢。它是计算密集型的,所以也许你需要更好的语言选择。而且,您不需要复数函数,只需要非常快速的方法来处理定点整数或 FPU 实数。
-
谢谢,问题似乎确实与数字的表示有关。当 Z 的模块大于 2 时将其更改为停止迭代已经使它更快。确保使用正确的数字表示应该照顾其余部分。我想我不习惯方案似乎会根据您对它们所做的操作神奇地改变数字的表示方式
-
所以基本上只需将
linspace中的常量2更改为2.0即可使整个事情在十分之一秒内完成。 @EtienneBoulais 这是一个错误。它只是对没有平方根的平方求和,所以该值确实应该是4
标签: recursion scheme lisp common-lisp mandelbrot