当前位置: 首页 > 知识库问答 >
问题:

为什么尾部递归gcd比rubinius的while循环快

乜坚成
2023-03-14

我有两个gcd函数的实现:

def gcd1(a,b)
  if a==b
    a
  elsif a>b
    if (a%b)==0
      b
    else
      gcd1(a%b,b)
    end
  else
    if (b%a)==0
      a
    else
      gcd1(a,b%a)
    end
  end
end
def gcd2(a,b)
  if(a==b)
    return a
  elsif b>a
    min,max=a,b
  else
    min,max=b,a
  end
  while (max%min)!=0
    min,max=max%min,min
  end
  min
end

函数gcd1是尾递归的,而gcd2使用的是时循环。

我已经验证了rubinius通过对阶乘函数进行基准测试来实现TCO,只有通过阶乘函数,基准测试才表明递归版本和迭代版本是“相同的ish”(我使用了基准测试IP)。

但对于上述情况,基准测试表明,gcd1比gcd2快至少两倍(递归比迭代快两倍,甚至更快)。

我用来基准测试的代码是这样的:

Benchmark.ips do |x|
  x.report "gcd1 tail recursive" do
    gcd1(12016,18016)
  end
  x.report "gcd2 while loop" do
    gcd2(12016,18016)
  end
  x.compare!
end

结果:

Warming up --------------------------------------
 gcd1 tail recursive    47.720k i/100ms
     gcd2 while loop    23.118k i/100ms
Calculating -------------------------------------
 gcd1 tail recursive    874.210k (± 7.1%) i/s -      4.343M
     gcd2 while loop    299.676k (± 6.6%) i/s -      1.503M

Comparison:
 gcd1 tail recursive:   874209.8 i/s
     gcd2 while loop:   299676.2 i/s - 2.92x slower

我正在运行Arch linux x64,处理器i5-5200 2.2 GHZ四核。

ruby实现是Rubinius 3.40。

那么递归怎么能比循环更快呢?

只是说fibonacci也有同样的情况:尾递归版本至少是循环版本的两倍,我用于fibonacci的程序:http://pastebin.com/C8ZFB0FR

共有1个答案

聂溪叠
2023-03-14

在这个例子中,你使用它只需要3次调用/循环就可以得到答案,所以我认为你实际上没有测试正确的东西。试着用两个连续的斐波那契数代替(例如第2000位和第2001位),结果应该相差不大。

(对不起,我还没有发表评论的名声)。

编辑:我终于安装了rubinius的[一部分],并设法重新创建了您所指的现象。这不是递归,而是多重赋值。如果你改变

      while n>0
        a,b=b,a+b
        n-=1
      end

      while n>0
        t=a
        a=b
        b=t+b
        n-=1
      end

while循环版本的性能应该(稍微)更快。这同样适用于原始GCD程序,即更换

        min,max=max%min,min

        t=min
        min=max%t
        max=t

就是这样。

ruby-2.1的情况并非如此,即while循环似乎更快,只是以您提供的形式。

希望有帮助!

 类似资料:
  • 我有一个(co?)递归函数对,它们处理元组列表,并根据一些开始和结束条件将它们折叠成批处理。 我做得不多,所以我可能很愚蠢。 我已经修改了一个简单的非尾部递归版本,通过明确引入一个“tot”参数来构成当前折叠状态,我认为这是尾部递归的,但我在大输入上得到了可怕的堆栈溢出。。。。(在调试器和(调试)中)。exe) 作为一个明确的折叠,可能有更好的方法来做到这一点...但这几乎不是重点,重点是为什么它

  • 我有一个关于如何将“递归”转换为“尾部递归”的问题。 这不是家庭作业,只是在我试图润色算法书籍中的递归定理时出现的一个问题。 我熟悉使用递归的两个典型示例(阶乘和斐波那契序列),也知道如何以递归方式和尾部递归方式实现它们。 我的代码如下(我使用Perl只是为了使其简单,但可以轻松地转换为C/Java/C)。 运行代码时,输出如下: 递归函数在返回之前使用不同的参数调用自己两次。我尝试了几种方法将其

  • 问题内容: 我正在经历 递增/递减运算符 ,并且 遇到了这样的情况:如果在这种情况下以递减形式运行循环,则其运行速度将比相同的以递增形式运行的循环快。 我期望两者将花费相同的时间,因为将遵循相同数量的步骤。我在网上搜索,但找不到令人信服的答案。是因为与增量运算符相比,减数运算符花费的时间更少吗? 问题答案: 这是因为在字节码中,与0比较与与非零数字比较是不同的操作。实际需要先将数字加载到堆栈上,然

  • 我想我的程序跳过了while循环,但我真的不确定到底发生了什么。该函数应该通过找到GCD,然后将分子和分母除以该数字来减少分数。 我得到分子和分母的绝对值,以确保如果分数是负数,我会在最后保持它。如果分子为0,则要求我返回(0,1)。问题是关于while循环。。。似乎它被完全跳过了。有什么建议吗?

  • 问题内容: 以下示例在Node.js书中给出: 解释了while循环为何阻止执行时,作者说: 节点将永远不会执行超时回调,因为事件循环卡在了循环中,而循环在第7行开始了,因此永远不会给它处理超时事件的机会! 但是,作者没有解释为什么这是在事件循环的背景下发生的,还是在幕后真正发生了什么。 有人可以详细说明吗?为什么节点卡住?以及如何在保留控制结构的同时更改上述代码,以使事件循环不会被阻塞,并且代码

  • 我想知道是否有一些通用方法可以用foo(…)转换“正常”递归foo(…) 作为尾部递归的最后一个调用。 例如(scala): 函数语言将递归函数转换为等价尾部调用的一般解决方案: 一种简单的方法是将非尾部递归函数包装在蹦床单子中。 所以pascal函数不再是递归函数。然而,蹦床单子是需要完成的计算的嵌套结构。最后,是一个尾递归函数,它遍历树状结构,解释它,最后在基本情况下返回值。 Rúnar Bj