【问题标题】:Map vs fold memory usage in OCamlOCaml 中的映射与折叠内存使用情况
【发布时间】:2020-09-07 04:25:41
【问题描述】:

折叠列表/数组时对内存使用的影响是什么,以及它与映射列表或数组时的内存使用相比如何(我的意思是执行类似List.map f l 的操作)?

我猜折叠会更昂贵,因为它会在每次迭代时创建“新”结果,并且 map 可以预先分配,但另一方面 map 不是尾递归的。

为简单起见,让我们考虑以下示例:

let lol = List.init 1000 (fun _ -> List.init 1000 (fun j-> j) );;

let fold_left_res = List.fold_left (fun res l -> List.map (fun e -> e + 1) l :: res) lol  [] ;;

let map_res = List.map (fun l -> List.map (fun e -> e + 1) l) lol ;;

let fold_right_res = List.fold_right (fun l res -> List.map (fun e -> e + 1) l :: res) lol  [];;

使用spacetime我已经对程序进行了剖析并获得了以下结果:

  1. 列表初始化后:

活动字节:22MB 活动块:1.0M 所有分配的字:3.0M

  1. fold_left 之后:

与列表初始化后相同(为什么?)

  1. 地图后

活字节:44MB 活块:2.0M 所有分配的字:6.0M

  1. fold_right 之后

活动字节:64MB 活动块:3.0M 所有分配的字:9.0M

为什么fold_left 似乎没有保留额外的内存? 为什么lol 首先会消耗这么多内存?

更新1。准备了一个更好的例子。

【问题讨论】:

    标签: memory ocaml


    【解决方案1】:

    通常,当您谈论内存使用时,您指的是堆。如您所见,堆栈和堆是两个不同的东西。

    对于堆栈,List.map 和 List.fold_right 都被记录为不是尾递归的。 (请参阅List module 文档。)因此您的测试结果符合预期。

    由于折叠基本上可以做任何事情,这取决于累积结果的性质,据我所知,实际上不可能对堆使用情况做出任何明确的决定。

    对于地图,通常的行为是为输入列表的每个元素返回独立的(不同的)新计算值。所以你可以说它将分配 N * K 的堆存储量,其中 N 是列表的长度,K 是计算值之一的大小。但实际上,地图功能也几乎可以做任何事情。它甚至不必分配新值,这只是一个粗略的概括。也许这就是您所说的可以预先计算地图的意思。

    如果您说的是返回列表的脊椎,则实际上无法事先计算。列表是不可变的,每次调用 List.map 都会创建一个新列表(结果的主干)。

    【讨论】:

    • “List.map 为每个调用创建一个新列表”是指每个函数 f 应用到 List.map f l 中的 l 元素?
    • List.map的返回值是一个列表。如果您将列表想象成一个“脊椎”,其上挂着像一条项链一样的值,那么List.map 的返回值的脊椎就是一个新创建的列表。挂在新列表中的值是对f 的单独调用返回的值。所以不,每次调用 f 都不会创建一个新列表。
    • 您能否具体说明一下,“List.map 为每次调用创建一个新列表(结果的主干)”是什么意思。 ?
    猜你喜欢
    • 1970-01-01
    • 2011-07-10
    • 1970-01-01
    • 2016-01-07
    • 1970-01-01
    • 2011-08-30
    • 1970-01-01
    • 2018-04-01
    • 1970-01-01
    相关资源
    最近更新 更多