openlang

对 (total, count) 二元组用了 foldl',常驻内存还是超过 1 GB?

我有个工作用的小工具(移动端应用),读取一份分析导出文件,是一个会话事件的 CSV,大约 600 万行,差不多 300MB,输出平均会话时长。结果是对的。但是跑完整文件时,我那台老 Intel MacBook 的风扇狂转,+RTS -s 显示最大驻留内存远超 1 GB。

我一开始用的是 foldl,后来换成 foldl',峰值内存几乎没变。cabal 文件里本来就有 -O2,也没什么实际差别。

meanMillis :: [Session] -> Double
meanMillis ss
  | n == 0    = 0
  | otherwise = fromIntegral total / fromIntegral n
  where
    (total, n) = foldl' step (0 :: Int, 0 :: Int) ss
    step (t, c) s = (t + sessMillis s, c + 1)

main 里是这样写的。

contents <- readFile (optPath opts)
let sessions = mapMaybe parseLine (drop 1 (lines contents))

Session 就是个普通 record,里面一个 String 和一个 Int,按屏幕过滤用的就是 filter。

我首先怀疑 readFile 和 lines,所以想过改成 hGetLine 循环,但还没试。如果文件是惰性读取的,每行解析完就丢掉,我不明白为什么严格 fold 还不够。这台机器上带 profiling 的构建特别慢,所以我基本是凭 -s 的数字在猜。

内存到底花到哪里去了?

从英语翻译查看原文

后续讨论 1

顺着彼此的依据读下去
  1. 固定链接

    我在另一个工具上也踩过同样的坑。foldl' 只会强制元组本身,不会强制里面的内容,所以 t + ... 和 c + 1 会堆成一堆 thunk。在 step 里加 bang pattern 就解决了,写成 step (!t, !c) s。(得开 BangPatterns 这个 pragma,毕竟就是这么麻烦。)立刻见效。

    从英语翻译查看原文

同一个问题的其他角度

  1. 空归约返回单位元

    对空向量做归约,APL 有答案。 每个原语都自带它的单位元。加法是 0,乘法和与是 1。⌈/⍬ 给出解释器里最小的负数。调用方不需要特殊处理,归约之前也不用检查长度。 dfn 就没有这种待遇。{⍺+⍵}/⍬ 在 Dyalog 里是 DOMAIN ER…

    回复 3最近活动 apl
  2. 我的大部分宏最后都变成了 const 和 static

    Nim 写宏太容易了,问题就出在这儿。 在动手写 macro 之前,先看看更便宜的工具。const 是在编译期求值的,用的就是跑宏的那个 VM,所以由普通 proc 构建的查找表直接写成 const table = buildTable() 就行。s…

    回复 3最近活动 nim
  3. it、_1,还有我仍在写的块参数名

    Ruby 3.4 为块参数加了 it,所以 users.map { it.name } 读起来几乎像一句话。我比较喜欢它,不太喜欢 _1,那东西总像是解析器随手丢下的。 Proc 文档里有几条规则值得知道。it 只存在于没有显式参数的块里。它是软关键…

    回复 0最近活动 ruby