在 OCaml 中的大型列表文本上编译期间堆栈溢出

Stack overflow during compilation on large list literal in OCaml

提问人:wojand 提问时间:11/13/2023 更新时间:11/15/2023 访问量:54

问:

我有一个小项目来检查一个数字是否是素数。这个想法是准备(生成)一个素数列表,直到某个点供库函数使用(为此使用代码生成器)。

虽然代码生成工作正常,但如果列表很大(例如,对于最多一百万个素数列表,即 ~75k 个条目),则生成文件的编译会失败。

沙丘建造输出:

enter image description here

dune build 输出的部分(底部)--verbose:enter image description here

一些代码:

(* prime_list.mli *)
val long_list : int list
(* THIS FAILS TO COMPILE: prime_list.ml (was generated, found only in _build directory) *)
let long_list = [2;3;5;7;
<a lot more entries, depending on parameter provided in dune file...>]

据我了解,列表字面意思是(这当然是糖,因为在编译过程中有太深的嵌套调用和堆栈溢出(可能是解析而不是构造值?可悲的是,我根本不知道编译在 OCaml 中是如何工作的)。2 :: (3 :: ( 5 :: ... )))::

你有什么办法解决吗?我想要一个列表,只要我愿意,使用尾递归编写每个函数,编译器就这样让我失望了......

回顾一下:我在内存中(在生成器中)有列表,但无法将其转储到文件(最好是 .ml)中,从而允许我在主程序中烘焙它,这令人沮丧。

出于绝望,我也尝试生成如下文件:

(* prime_list.ml, another try *)
let long_list = []
let long_list = 999983 :: long_list
let long_list = 999979 :: long_list
...
let long_list = 3 :: long_list
let long_list = 2 :: long_list

以及

let long_list0 = []
let long_list1 = 999983 :: long_list0
let long_list2 = 999979 :: long_list1
...
let long_list78487 = 3 :: long_list78496
let long_list78498 = 2 :: long_list78497
let long_list = long_list78498

但这两种方法都导致了同样的事情()。我是OCaml的新手,我不明白为什么这不起作用。我对这个问题的答案很感兴趣——可能有点猜测。当然,除了解决我的问题。Fatal error: exception Stack overflow

也许我可以以某种方式更改堆栈的限制?从这个角度(编译器设置和标志)的答案将不胜感激,但这并不能真正令人满意作为最终答案,因为它可能不会无限扩展(不过我可能错了,谁知道呢?

以下是 lib/dune 文件内容,以确保完整性:

; primes_with_generator/lib/dune
(library
 (name primes_with_generator)
 (libraries commons))

(rule
 (target prime_list.ml)
 (deps    (:gen ../generator/gen.exe))
 (action  (with-stdout-to %{target} (run %{gen} 1000000))))

1_000_000 是这里的一个参数 -- 生成最多 1_000_000 的素数。对于生成高达 100_000 的素数,一切都很好,但我希望素数高达 sqrt(INT MAX) ≈ 2^32 ≈ 10^9...好吧,至少不想因为这样的愚蠢原因而停在 ~200k。

Commons 可能无关紧要,但这里是它的界面供您查看:

(* commons/prime.mli *)

(* given arguments:
 - list of primes up to a point, should be in ascending order, may be empty),
 - a number n >= 2
   returns answer to the question: is number n prime?
*)
val is_prime : int list -> int -> bool
列出 编译器错误 OCAML 代码生成 OCAML-DUNE

评论

1赞 Shawn 11/13/2023
使用序列化到文件并在运行时读回?Bigarray.Array1
1赞 Shawn 11/13/2023
(Unix.map_file这种方法会派上用场)
0赞 Chris 11/13/2023
这是题外话,但如果你使用已知素数列表来测试一个 int 是否是素数,你确定想要一个列表吗?查找将是 O(n),而不是哈希表之类的东西。
0赞 Chris 11/13/2023
或者可能是一套。
0赞 wojand 11/14/2023
我正在测试 n 是否可以被它们整除。对于最大素数,比如 100_000,我可以更有效地检查任何不超过 10_000_000_000 的数字是素数(目前对于大于 max_p^2 的数字,算法有时(如果没有找到小除数)在那之后检查连续数字的可整除性)。另外,我什至不知道如何做这些事情哈哈

答:

1赞 Jeffrey Scofield 11/13/2023 #1

我尝试定义一个包含 75,000 个 int 的列表,但编译器确实在编译过程中用完了堆栈空间。这很有趣,但我认为必须对程序进行一些限制。

我猜想你对单独构建列表的问题是编译器试图进行不断折叠,即在编译时创建相同的大列表。

通过在运行时进行串联,我能够获得 75,000 个整数的列表。

从本质上讲,我定义了 75 个列表,每个列表有 1000 个整数。然后,我定义了一个类型的值,其中包含每个单独的列表。int list list

let sublist1 = [1; 2; ...; 1000 ]
. . .
let sublist75 = [1; 2; ...; 1000 ]

let list_of_lists = [ sublist1; sublist2; ...; sublist75 ]

最后我有:

let long_list = List.concat list_of_lists

main 函数如下所示:

let main () = Printf.printf "%d\n" (List.length long_list)

let () = main ()

当我运行它时,我看到这个:

$ ./big
75000

评论

0赞 wojand 11/14/2023
这个想法困扰我的一件事是,我将不得不在某个时候划分子列表,并且不知道如何自动编排它,尤其是在一种僵化的类型系统中。OTOH 也许一两个不必要的巢穴不会造成伤害并且足够?写这篇评论时,我记得我可以在生成过程中做任何可能的事情,所以假设某个数字(prolly ca 50k)是安全的,以最佳方式做到这一点听起来很有趣。再次感谢你!
0赞 wojand 11/14/2023
将研究编译器优化/性能,并在之后也在此处留言
2赞 octachron 11/13/2023 #2

快速而肮脏的解决方案是增加堆栈大小(在 linux 上或切换到 OCaml 5)。ulimit -s unlimited

可扩展的解决方案是不混合代码和数据。质数表是数据,应该这样存储。作为一种快速存储格式,您可以使用 Marshal 模块。如果出于某种原因,您确实不想拥有单独的数据文件,则可以将数据存储为字符串:

let primes: int array =
  Marshal.from_string
    "\132\149\166\190\000\000\000\006\000\000\000\001\000\000\000\006\000\000\000\006\208BCEGK"
    0

它应该一直工作到数组太大而无法完全保留在内存中。

编辑: 为了从文件加载数据,您可以使用 which 自行负责打开和关闭通道(即使引发异常)In_channel.with_open_bin

let load filename: int array =
  In_channel.with_open_bin filename @@ fun chan ->
  Marshal.from_channel chan

评论

0赞 wojand 11/14/2023
我以为我正在使用 OCaml 5,但我真的不是。哇,很高兴知道。你马上就知道了吗?尊重:) #(我的机器上有 OCaml 5,但在大学的电脑上通过 ssh 工作)# 我现在很好奇这是否是我们的教授故意策划的,以宣传 OCaml 5 有多好以及 OCaml 如何变得更好哈哈
0赞 wojand 11/14/2023
去尝试一下,让你知道它是怎么回事,但看起来不错
0赞 wojand 11/14/2023
我相信它拼写为元帅?这就是我在谷歌搜索时看到的,如果我错了,请纠正我
0赞 wojand 11/14/2023
工作得很好,虽然花了我一点磕磕绊绊来弄清楚如何打开通道,获取模块暴露的值,关闭通道。
0赞 wojand 11/14/2023
我的 C 心不想在运行时打开文件,而是静态地烘烤它,这可能是不合理的,但很高兴知道如何去做。不过,我现在得到了一条非常丑陋的绝对路径:/