我在OCaml中有一个递归函数,该函数接受一个表达式并返回指令数组(这是针对编译器项目的)。
代码基本上看起来像
let rec compile_body (body:expr):fn =
match body with
| Seq (expr1, expr2) -> Array.concat [compile_body expr1; compile_body expr2;]
| Add (n1, n2) -> [|Load 0 n1; Load 1 n2; Add 0 0 1;|]
| ...
将函数编写为返回列表,然后最后将列表转换为数组,是否会更有性能?
答案 0 :(得分:1)
Array.concat
或Array.append
,根据文档,分配一个新数组,然后在后面复制两个初始数组的内容。
List.append
的复杂度仅与第一个参数的长度成比例。
因此,从理论上讲,使用list应该更有效,因为最后一个数组串联将与最后的Array.of_list
操作具有相同的复杂性。
edit:如@coredump所述,保留一棵数组树(该数组代表所有数组的逻辑串联)将是较便宜的结构,如果最后需要一个实际的数组,则可以计算总大小您的“抽象”数组的大小,创建此大小的数组,然后用数组树的内容填充它。该操作的最终数组大小将是线性的。
话虽这么说,但我怀疑除非您有丰富的表达要编译,否则最终是否会有很大的不同。在这种情况下,您还应该注意List.append
不是尾递归的事实,因此可能会导致(确实)大列表上的堆栈溢出。
答案 1 :(得分:1)
使用数组会一遍又一遍地复制整个内容。很快变得太慢了。
轻松使用带有append的列表存在相同的问题,甚至更糟。切勿在长列表中添加任何内容。订购代码,以便将其追加到简短列表中。 rev_append也可能有用。但是在您的情况下,Seq (a, b)
可以为a
和b
设置很长的列表,因此追加是个坏主意。
解决方案是使用列表,但不使用追加。从右到左遍历语法树并从末尾开始构建列表。这样,所有操作都只会添加到列表的最前面。
或者进行从左到右遍历以向后构建列表,最后将其反向。当您按照预期的顺序构造事物时,这通常更容易阅读。