【问题标题】:perl6: Cannot unbox 65536 bit wide bigint into native integerperl6:无法将 65536 位宽的 bigint 拆箱为本机整数
【发布时间】:2019-02-05 06:36:16
【问题描述】:

我尝试了Rosettacode 中的一些示例,但在提供的 Ackermann 示例中遇到了问题:当“未修改”运行它时(我将 utf-8 变量名替换为 latin-1 变量名),我得到(类似,但现在可复制):

$ perl6 t/ackermann.p6
65533
19729 digits starting with 20035299304068464649790723515602557504478254755697...
Cannot unbox 65536 bit wide bigint into native integer
  in sub A at t/ackermann.p6 line 3
  in sub A at t/ackermann.p6 line 11
  in sub A at t/ackermann.p6 line 3
  in block <unit> at t/ackermann.p6 line 17

删除第 3 行中的 proto 声明(通过注释掉):

$ perl6 t/ackermann.p6
65533
19729 digits starting with 20035299304068464649790723515602557504478254755697...
Numeric overflow
  in sub A at t/ackermann.p6 line 8
  in sub A at t/ackermann.p6 line 11
  in block <unit> at t/ackermann.p6 line 17

出了什么问题?该程序不会分配太多内存。自然整数是有限的吗?

我在Ackermann function的代码中替换了????与 m 和 ????使用 n 以获得更好的终端交互以复制错误并尝试注释掉 proto 声明。我也问过丽兹;)

use v6;

proto A(Int \m, Int \n) { (state @)[m][n] //= {*} }

multi A(0,      Int \n) { n + 1 }
multi A(1,      Int \n) { n + 2 }
multi A(2,      Int \n) { 3 + 2 * n }
multi A(3,      Int \n) { 5 + 8 * (2 ** n - 1) }

multi A(Int \m, 0     ) { A(m - 1, 1) }
multi A(Int \m, Int \n) { A(m - 1, A(m, n - 1)) }

# Testing:
say A(4,1);
say .chars, " digits starting with ", .substr(0,50), "..." given A(4,2);

A(4, 3).say;

【问题讨论】:

    标签: biginteger raku


    【解决方案1】:

    请先阅读 JJ 的回答。它很轻松,导致了这个答案,实际上是对它的阐述。

    TL;DR A(4,3) 是一个非常大的数字,在这个宇宙中无法计算。但是 raku(do) 会尝试。如果您使用缓存版本,您将超出与内存分配和索引相关的合理限制,如果不使用,则会超出与数字计算相关的限制。


    我尝试了一些来自 Rosettacode 的示例,但遇到了 the provided Ackermann example 的问题

    引用任务描述并添加一些强调

    任意精度是首选(因为函数增长如此之快

    raku 的标准整数类型Intarbitrary precision。 raku 解决方案使用它们来计算可能的最高级答案。只有当你让它尝试做不可能的事情时它才会失败。

    当“未修改”运行时(我用 latin-1 替换了 utf-8 变量名)

    替换变量名并不是一个重大变化。

    但添加 A(4,3) 行会将代码从现实中可计算变为现实中不可计算。

    您修改的示例只有一个解释性注释:

    这是一个缓存版本... 使 A(4,2) 成为可能

    请注意,A(4,2) 解决方案的长度接近 20,000 位。

    如果您查看该页面上的其他解决方案,大多数人甚至都不会尝试联系A(4,2)。 Phix 版上有这样的 cmets:

    优化。仍然没有 bignum 库,所以 ack(4,2),即 power(2,65536)-3,显然是 19729 位,以及任何以上,都超出了(CPU/FPU 硬件)和这个 [code]。

    A(4,2) 的解决方案是最先进的。

    A(4,3) 在实践中是不可计算的

    引用Academic Kids: Ackermann function:

    即使对于较小的输入(例如 4,3),阿克曼函数的值也会变得如此之大,以至于无法对其进行计算,实际上它们的十进制展开式甚至无法存储在整个物理宇宙中。

    所以计算A(4,3).say 是不可能的(在这个宇宙中)。

    必须不可避免地导致任意精度整数运算的溢出。这只是时间和方式的问题。

    Cannot unbox 65536 bit wide bigint into native integer

    第一条错误信息提到了这行代码:

    proto A(Int \m, Int \n) { (state @)[m][n] //= {*} }
    

    state @anonymous state array variable

    默认情况下,@ 变量使用 the default concrete type 表示 raku 的 abstract array type。这种默认数组类型提供了实现复杂性和良好性能之间的平衡。

    在计算 A(4,2) 时,索引(mn)仍然足够小,以至于计算完成时不会超出默认数组的索引限制。

    这个限制是一个“本地”整数(注意:不是"natural" integer)。 “本机”整数是 raku 所称的由其运行的硬件支持的固定宽度整数,通常是 long long,通常是 64 位。

    64 位宽的索引可以处理高达9,223,372,036,854,775,807 的索引。

    但在尝试计算 A(4,3) 时,该算法会生成一个 65536 位(8192 字节)宽的整数索引。这样的整数可以大到 265536,即20,032 decimal digit number。但允许的最大索引是 64 位本机整数。所以除非你注释掉使用数组的缓存行,否则对于A(4,3),程序最终会抛出异常:

    无法将 65536 位宽的 bigint 拆箱为原生整数

    默认数组类型的分配和索引限制

    正如已经解释的那样,没有足够大的数组来帮助完全计算A(4,3)。此外,64 位整数已经是一个相当大的索引 (9,223,372,036,854,775,807)。

    也就是说,raku 可以适应其他数组实现,例如 Array::Sparse,所以我将在下面简要讨论这一点,因为这些可能性可能对其他问题感兴趣。

    但在讨论更大的数组之前,running the code below on tio.run 显示了该平台上默认数组类型的实际限制

    my @array;
    @array[2**29]++; # works
    @array[2**30]++; # could not allocate 8589967360 bytes
    @array[2**60]++; # Unable to allocate ... 1152921504606846977 elements
    @array[2**63]++; # Cannot unbox 64 bit wide bigint into native integer
    

    (注释掉错误行以查看以后/更大的错误。)

    “无法分配 8589967360 字节”错误是 MoarVM 恐慌。这是 tio.run 拒绝内存分配请求的结果。

    我认为“无法分配...元素”错误是由于超出某些内部 Rakudo 实现限制而引发的 raku 级别异常。

    最后一条错误消息显示默认数组类型的索引限制,即使有大量内存可供程序使用。

    如果有人想做更大的索引怎么办?

    可以创建/使用支持稀疏数组等的其他 @ (does Positional) 数据类型。

    而且,使用这种机制,有人可能会编写一个数组实现,它支持比默认数组类型支持的更大的整数索引(可能是通过在底层平台指令之上分层逻辑;也许Array::Sparse I上面的链接确实)。

    如果这样的替代方法被称为BigArray,那么缓存行可以替换为:

    my @array is BigArray;
    proto A(Int \?, Int \?) { @array[?][?] //= {*} }
    

    同样,这仍然不足以存储临时结果以完全计算A(4,3),但我的意思是展示自定义数组类型的使用。

    Numeric overflow

    当你注释掉你得到的缓存时:

    数值溢出

    Raku/Rakudo 做任意精度的算术。虽然这有时被称为无限精度,但它显然不是实际上无限的,而是“任意”,在这种情况下,对于“理智”的某些定义,这也意味着“理智”。 p>

    这通常意味着存储数字的内存不足。但在 Rakudo 的情况下,我认为在完全耗尽 RAM 之前,尝试通过从真正巨大的 Int 切换到 Num(浮点数)来保持理智。但随后计算 A(4,3) 最终会溢出甚至是双浮点数。

    因此,虽然缓存很快就会爆炸,但无论如何代码肯定会在以后爆炸,然后你会得到一个数字溢出,它要么表现为内存不足错误,要么表现为数字溢出错误,就像在这个案例。

    【讨论】:

    • 感谢您的详细解释。 jjmerelo 给出的答案对我来说已经足够解释了——但你挖掘得更深,并给出了很多合理的想法来了解更多关于 rakudo 的信息。
    • Yw,感谢您的反馈,希望您喜欢探索/使用 P6/Rakudo。
    • 我等了一段时间才有机会。现在我必须使用 cpu 时间和内存以不同的方式模拟压力。 P6/Rakudo 帮助很大。但是阿克曼死得很快;)
    • “但是 Ackermann 死得太快了” :) 几个想法... 1 找到或实现一个或三个匹配来自 Big O Cheat Sheet 的算法和数据结构的算法。 2 访问freenode IRC 聊天频道MoarVM,方法是点击here,然后点击开始按钮,然后输入.ask timotimo Have you got any suggestions for P6 code that would stress cpu and memory in interesting ways? 或类似内容。
    • 感谢您的提议 - 目前我有一些算法可以完成这项工作;)一些纯 CPU、一些内存和 CPU、一些 I/O 和 CPU。 Perl6 社区提供的示例是多方面的。我只是想知道为什么阿克曼会以这种(对我而言)难以理解的方式死去——学习一些关于 P6 的知识并且不要两次犯同样的错误......
    【解决方案2】:

    数组下标使用native ints;这就是为什么当您使用大整数作为数组下标时在第 3 行中出现错误的原因。您可能必须定义一个使用 Ints 作为数组下标的新 BigArray。

    第二个问题出现在**操作符中:结果是Real,当低级操作返回Num时,会抛出异常。 https://github.com/rakudo/rakudo/blob/master/src/core/Int.pm6#L391-L401

    因此,无论如何创建 BigArray 可能都没有帮助。您还必须创建自己的 **,它始终适用于 Int,但您似乎已经达到了无限精度 Int 的(不是无限的)限制。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-12-30
      • 1970-01-01
      • 2019-09-12
      • 2012-11-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多