【问题标题】:How to fix aborting Fibonacci sequence code如何修复中止的斐波那契序列代码
【发布时间】:2017-03-15 18:51:39
【问题描述】:

我正在尝试获得 500 万个元素的斐波那契数列。

当我将 1000 作为参数传递时,此代码异常中止。

def self.fibo_seq(limit)
  result_array = [0,1]
  return result_array if limit < 2
   while result_array.length <= limit
     result_array << result_array[-1] + result_array[-2]
   end
  return result_array
end
res= Multiple.fibo_seq(5_000_000)
print res

Error: [1]    22382 killed     ruby fibo.rb

示例输出:

# >> [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, , 1...] upto 5 Million elements

【问题讨论】:

  • 溢出,很可能。在大多数语言中,您不能只在变量中使用大量数字。
  • @RadialApps:在 ruby​​ 中你可以。没有任何问题。
  • @SergioTulentsev,我猜你是对的(只是查了一下)。我的第二个猜测是当它变得足够大时,内存不足以分配给更大的数据类型。
  • 巨大的数字存储在内存中的成本很高,运行程序并打开另一个选项卡并运行top -o mem
  • 代码应该正确处理 1000。顺便说一句,50_00_000 是 5,000,000,如果您要使用该符号,则应写为 5_000_000。编程时,细节很重要。

标签: ruby-on-rails arrays ruby memory-leaks fibonacci


【解决方案1】:

在 64 位平台上,使用 YARV 的 Integer 实现存储前 5000000 个斐波那契数恰好使用 1084762047712 字节(假设每个字节 8 位)。这接近 1 TiByte(准确地说是 0.9865853351 TiByte)。这只是数字本身的空间,还有数组的开销(几个字节)和数组内的指针(略小于 5000000 乘以 8,或略大于 38 MiByte)。

计算这 5000000 个数字甚至 存储它们(只记住最后 2 个以避免重新计算),在我 2011 年末的 MacBook Pro 上花了 20 多分钟。在分配 1 TiByte RAM 的同时计算它们会慢得多。如果您没有 1 TiByte 的 RAM,并且操作系统开始换出到磁盘,那么即使您有一个通过 FibreChannel 连接的 SSD RAID,它也会慢几个数量级。

为了打印数组,首先需要将其表示为字符串。即使只是逗号和空格没有,数字已经是 4999999*2 个字符,需要接近 10 MiByte 的 RAM(假设是单字节字符集)。如果您尝试仅打印逗号和空格,则需要大约 2500 页 DIN A4 纸,如果您双面打印,则需要 1250 页。办公用纸通常以一叠 500 张约 5 厘米高的形式出售,因此您有 2.5 叠约 12.5 厘米高仅用于逗号和空格

5000000 个数字的总位数以及字符(和字节)大约为 2.7 万亿位,即要打印的最终字符串大约需要 2.5 TiByte 的 RAM。将其打印在 DIN A4 双面上将产生一叠 33 公里高的纸,是珠穆朗玛峰高度的 4 倍。

总而言之,在您调用 print 时,您的程序需要大约 3.5 TiByte 的 RAM。

打印到控制台实际上非常慢,在我的标准 macOS Terminal.app 上,我得到大约 1 MiByte/s,这意味着不仅计算 5000000 个数字需要至少数十分钟​​,甚至不用计算时间分配所有这些对象和所有 RAM,不仅您的程序将使用 3.5 TiByte 的 RAM,而且在终端上显示最终数组的操作将花费大约 一个月

tl;dr 摘要:5000000 个斐波那契数很大

【讨论】:

    【解决方案2】:

    这个程序的问题很可能是内存限制。但是您真的需要所有这些数字吗?如果是,那么您最好获得更多硬件。

    否则,如果您只需要序列中的百万分之五的数字,您可以通过只存储最后两个数字来大大加快程序的速度。

    改进的最后一步:在常数时间内计算斐波那契数列的任意成员! - “Find The Millionth Fibonacci in Java”。

    【讨论】:

      【解决方案3】:

      生成一个 5M 的斐波那契数列是一个问题,因为涉及到内存和时间。

      生成它后,下一个问题变成了重用这些结果,因此您不必重复执行两次。即使它适合,将序列存储在内存中也是愚蠢的,因为代码或机器崩溃将强制重新生成值,如果您需要 5,000,000,您可能需要等待很长时间才能让应用程序准备好做一些有用的事情,所以把它们在磁盘上,可以是平面文件,也可以是数据库,您可以相对快速地检索所需的特定值。

      下面是生成平面文件的简单代码,我测试了多达 25,000 个文件,然后才感到无聊并停止了它。它似乎对那个测试做得很好,但我想它会随着 Ruby 的改变而变慢。上限是多少我不知道,也没有耐心去查。

      limit = ARGV.shift.to_i
      
      puts "#{limit} iterations"
      
      File.open('fibonacci.out', 'w') do |fo|
        ary = [0, 1]
        fo.puts ary
        break if limit < 2
      
        (limit - ary.length).times do |i|
          next_nbr = ary[-1] + ary[-2]
          ary.shift
          ary.push(next_nbr) 
      
          fo.puts next_nbr
          print 2 + i, "\r"
        end
      
        puts
      end
      

      摆脱ary 可能会加快速度。

      运行它

      ruby test.rb 5
      

      导致“fibonacci.out”包含:

      0
      1
      1
      2
      3
      

      这似乎是对的。

      有用于数据库的斐波那契生成器,但如果它们是递归的,您最终会取出 DBM,因为它会尝试生成大数字,因此使用简单的生成器然后将值存储到表中似乎更合理。

      【讨论】:

        【解决方案4】:

        这可能比替代方案更好。第 5 百万个斐波那契数将有大约一百万位数字。忽略计算时间,存储所有这些将占用 1 TB 的内存和至少另外 2 TB 的内存或存储用于输出。

        归根结底,如果你想这样做,你不能在普通台式机上这样做,也不应该在 Ruby 中这样做。

        对于那些问我是如何得到号码的人:

        根据 Wikipedia https://en.wikipedia.org/wiki/Fibonacci_number#Magnitude,位数约为 n 的 0.2090 倍,因此对于 5,百万分之一的数字约为一百万位数。我没有仔细研究过 Ruby 的 BigNumber 实现,但我假设每个字节有 2 位数字,这是十进制算术的最简单表示。您可以在其中添加更多位(10 位中的 3 位而不是 8 位中的 2 位),但它不会在这里改变太多的结果。

        对于整个数组,我只是使用算术级数总和的标准公式。 (n/2)*(a0 + an):5,000,000 / 2 * 1,000,000 或 2.5e12 位。以每字节 2 位计算,这将是大约 1 TB 的内部内存(不计算 Ruby 为其内部结构和间接添加的开销)。

        如果您将其打印或存储,您可以计算每个字节 1 位数字(在 UTF-8 中),这样将占用 2.5 TB,不包括 4,999,999 个逗号。

        【讨论】:

        • 如何计算 1-2 TB?不是说你错了,但这个数字背后的数学原理是什么?
        • @Glyoko:以b为基数的数字表示的长度大致为log_b(n)。十进制和二进制之间的转换因子大约是 3 到 10 (10^3 == 1000, 2^10 == 1024)。 fib(5000000) 大约是 7*10^1044000,因此超过一百万个十进制数字,转换为大约 347 万位,或 423 KiByte。不确定 1 TiByte 数字来自哪里。我认为 Marc 指的是整个数组的大小。我只是在自己处理数字。
        • 我认为您将 Ruby 的 BigDecimal (任意精度的 decimal 类型)与 Ruby 的 Integer (任意大小的 binary 整数)混淆了类型。 Integer 每个字节恰好使用 8 个(二进制)数字,或大约 2.5 个十进制数字。但是,就像您自己写的那样,这对您的论点没有太大影响。
        • 另一个有趣的事实:在我的机器上(2011 年末 MacBook Pro 和标准 macOS Terminal.app)在终端上显示数组大约需要一个月时间。
        • Jörg,不,我的意思是 Bignum,尽管 Ruby 2.4 删除了它,但实现仍然在幕后。您是对的,尽管它是作为位串实现的。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-04-21
        • 2010-09-22
        • 1970-01-01
        相关资源
        最近更新 更多