简体   繁体   English

效率:递归 vs 循环

[英]Efficiency: recursion vs loop

This is just curiosity on my part, but what is more efficient, recursion or a loop?这只是我的好奇心,但是递归或循环哪个更有效?

Given two functions (using common lisp):给定两个函数(使用普通的 lisp):

(defun factorial_recursion (x)
    (if (> x 0)
        (* x (factorial_recursion (decf x)))
        1))

and

(defun factorial_loop (x)
    (loop for i from 1 to x for result = 1 then
        (* result i) finally
        (return result)))

Which is more efficient?哪个更有效率?

I don't even have to read your code.我什至不必阅读您的代码。

Loop is more efficient for factorials.循环对于阶乘更有效。 When you do recursion, you have up to x function calls on the stack.进行递归时,堆栈上最多有x 个函数调用。

You almost never use recursion for performance reasons.出于性能原因,您几乎从不使用递归。 You use recursion to make the problem more simple.您使用递归使问题更简单。

Mu.亩。

Seriously now, it doesn't matter.现在说真的,没关系。 Not for examples this size.不是这个尺寸的例子。 They both have the same complexity.它们都具有相同的复杂性。 If your code is not fast enough for you, this is probably one of the last places you'd look at.如果您的代码对您来说不够快,这可能是您最后要看的地方之一。

Now, if you really want to know which is faster, measure them.现在,如果您真的想知道哪个更快,请测量它们。 On SBCL, you can call each function in a loop and measure the time.在 SBCL 上,您可以循环调用每个函数并测量时间。 Since you have two simple functions, time is enough.既然你有两个简单的函数, time就够了。 If your program was more complicated, a profiler would be more useful.如果您的程序更复杂,分析器会更有用。 Hint: if you don't need a profiler for your measurements, you probably don't need to worry about performance.提示:如果您的测量不需要分析器,您可能不需要担心性能。

On my machine (SBCL 64 bit), I ran your functions and got this:在我的机器(SBCL 64 位)上,我运行了你的函数并得到了这个:

CL-USER> (time (loop repeat 1000 do (factorial_recursion 1000)))
Evaluation took:
  0.540 seconds of real time
  0.536034 seconds of total run time (0.496031 user, 0.040003 system)
  [ Run times consist of 0.096 seconds GC time, and 0.441 seconds non-GC time. ]
  99.26% CPU
  1,006,632,438 processor cycles
  511,315,904 bytes consed

NIL
CL-USER> (time (loop repeat 1000 do (factorial_loop 1000)))
Evaluation took:
  0.485 seconds of real time
  0.488030 seconds of total run time (0.488030 user, 0.000000 system)
  [ Run times consist of 0.072 seconds GC time, and 0.417 seconds non-GC time. ]
  100.62% CPU
  902,043,247 processor cycles
  511,322,400 bytes consed

NIL

After putting your functions in a file with (declaim (optimize speed)) at the top, the recursion time dropped to 504 milliseconds and the loop time dropped to 475 milliseconds.将您的函数放在以(declaim (optimize speed))开头的文件中后,递归时间下降到 504 毫秒,循环时间下降到 475 毫秒。

And if you really want to know what's going on, try dissasemble on your functions and see what's in there.如果您真的想知道发生了什么,请尝试对您的功能进行dissasemble ,看看里面有什么。

Again, this looks like a non-issue to me.同样,这对我来说似乎不是问题。 Personally, I try to use Common Lisp like a scripting language for prototyping, then profile and optimize the parts that are slow.就我个人而言,我尝试像使用脚本语言一样使用 Common Lisp 进行原型设计,然后分析和优化速度较慢的部分。 Getting from 500ms to 475ms is nothing.从 500 毫秒到 475 毫秒没什么。 For instance, in some personal code, I got a couple of orders of magnitude speedup by simply adding an element type to an array (thus making the array storage 64 times smaller in my case).例如,在一些个人代码中,我通过简单地将元素类型添加到数组中获得了几个数量级的加速(因此在我的情况下使数组存储空间小 64 倍)。 Sure, in theory it would have been faster to reuse that array (after making it smaller) and not allocate it over and over.当然,从理论上讲,重用该数组(在使其变小之后)而不是一遍又一遍地分配它会更快。 But simply adding :element-type bit to it was enough for my situation - more changes would have required more time for very little extra benefit.但是对于我的情况,简单地添加:element-type bit就足够了 - 更多的更改需要更多的时间来获得很少的额外好处。 Maybe I'm sloppy, but 'fast' and 'slow' don't mean much to me.也许我很草率,但“快”和“慢”对我来说意义不大。 I prefer 'fast enough' and 'too slow'.我更喜欢“足够快”和“太慢”。 Both your functions are 'fast enough' in most cases (or both are 'too slow' in some cases) so there's no real difference between them.在大多数情况下,您的两个函数都“足够快”(或者在某些情况下两者都“太慢”),因此它们之间没有真正的区别。

If you can write recursive functions in such a way that the recursive call is the very last thing done (and the function is thus tail-recursive ) and the language and compiler/interpreter you are using supports tail recursion, then the recursive function can (usually) be optimised into code that is really iterative, and is as fast as an iterative version of the same function.如果您可以以递归调用是最后完成的方式编写递归函数(因此该函数是尾递归的并且您使用的语言和编译器/解释器支持尾递归,那么递归函数可以(通常)被优化为真正迭代的代码,并且与同一函数的迭代版本一样快。

Sam I Am is correct though, iterative functions are usually faster than their recursive counterparts. Sam I Am 是正确的,但迭代函数通常比递归函数更快。 If a recursive function is to be as fast as an iterative function that does the same thing, you have to rely on the optimiser.如果递归函数要与执行相同操作的迭代函数一样快,则必须依赖优化器。

The reason for this is that a function call is much more expensive than a jump, plus you consume stack space, a (very) finite resource.这样做的原因是,一个函数调用比跳贵得多,再加上你消耗的堆栈空间,一个(非常)有限的资源。

The function you give is not tail recursive because you call factorial_recursion and then you multiply it by x .您给出的函数不是尾递归的,因为您调用了factorial_recursion然后将其乘以x An example of a tail-recursive version would be尾递归版本的一个例子是

(defun factorial-recursion-assist (x cur)
    (if (> x 1)
        (factorial-recursion-assist (- x 1) (+ cur (* (- x 1) x)))
        cur))

(defun factorial-recursion (x)
    (factorial-recursion-assist x 1))

(print (factorial-recursion 4))

Here's a tail-recursive factorial (I think):这是一个尾递归阶乘(我认为):

(defun fact (x)
  (funcall (alambda (i ret)
             (cond ((> i 1)
                    (self (1- i) (* ret i)))
                   (t
                    ret)))
           x 1))

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM