Lisp - 使用低内存占用写入文件

Posted

技术标签:

【中文标题】Lisp - 使用低内存占用写入文件【英文标题】:Lisp - write to file using low memory footprint 【发布时间】:2020-09-30 16:51:48 【问题描述】:

我有大型哈希表,我将其写入磁盘作为偶尔的备份。我发现当我映射哈希表并写入文件时,与哈希大小相比,RAM 使用量猛增。

我在带有 slime 和 sbcl 2.0.3.176 的 emacs 上运行 lisp。系统是戴尔服务器上的 Ubuntu 19.10。

数据是多层次的哈希表。它的基本结构是:

customer-ht - 称为 customer 的结构哈希表,以整数列表为键,例如 (1 2)、(1 3)

(defstruct customer
  (var1 0)
  (var2 (make-hash-table))
  (var3 (make-hash-table)))

var2 哈希表是简单的键/值,其中键是整数 1、2 等,值始终为 'T

var3 哈希表的键是整数,它的值是另一个哈希表,其中键是整数列表 (1 2 3) (1 5 7),值始终是 'T

所以,客户 (1 2) 有

var1 = 5,

var2 = 键 3 的哈希表,值 'T

var3 = 键 9 的哈希表,值 = 键 (5 6 7) 的哈希表,值 'T

我正在使用它来映射和写入文件:

(defun write-cust-to-file (filename)
  (with-open-file (s filename
                    :direction :output
                    :if-exists :supersede)
    (maphash
      #'(lambda (cust-key cust-data)
          (format s "~A ~A~%" cust-key customer-var1)
          (maphash
           #'(lambda (k1 v1)
               (declare (ignore v1))
               (format s "~A ~A~%" cust-key k1))
           (customer-var2 cust-data))
          (maphash
           #'(lambda (k1 v1)
               (maphash
                #'(lambda (k2 v2)
                    (declare (ignore v2))
                    (format s "~A ~A~%" (list cust-key "X" k1) k2))
                v1))
           (customer-var3 cust-data)))
      customer-ht))
  nil)

结构中有更多的变量,比如这些,都是使用相同的 maphash/write 代码编写的。因此,每个客户结构都非常大。

当我运行它时,我的 RAM 会爆炸。我在 RAM 中的所有数据都在 20GB 左右。当我运行它时,它达到 40GB+。我开始认为 maphashes 在运行时会从结构中复制数据。如果我对上面的 maphash 部分运行类似的写入函数,在没有结构的哈希上使用 k1 和 k2(2 个嵌套映射),则不会发生内存增加。

有没有一种方法可以在不使用任何额外 RAM(或至少很少)的 LISP 中写入文件?我会降低性能以节省我的 RAM。

附加信息:我在运行此程序时运行了 dstat,发现写入磁盘不是连续的。它大约每 30 秒写入一个大块 (20MB-120MB),每 5 秒左右写入 12K 小块。此外,RAM 使用量在函数完成写入之前达到顶峰。那么,数据在等待写入磁盘时是否存储在某处?还是只是分配一些内存?运行 (gc :full 'T) afterword 会恢复所有额外的 RAM。

【问题讨论】:

每个问题报告都有:请提供详细信息。 LISP 可以是一百种实现中的任何一种,没有提到操作系统,没有可重复的例子...... @Mark:是的,我认为这更好。谢谢你。我原来的评论已经没有了,因为它现在没有意义了。 你的代码分配了lambda闭包,还有(list cust-key "X" k1) @Kaz:但我认为这些应该(应该......)都是相当短暂的,所以 GC 应该把它们捡起来,不是吗? @tfb 确实如此!在问题中,OP 写道“Running (gc :full 'T) afterword 恢复了所有额外的 RAM。” 【参考方案1】:

这不是一个完整的答案。我认为导致泄漏的原因是 SBCL 特定的,所以最好的办法可能是找出 SBCL 人在哪里闲逛(假设它不在这里)并询问他们。

但是要做的一件事是检测 GC 以查看您是否可以弄清楚发生了什么。例如,您可以这样做:

(defun dribble-gc-info ()
  (format *debug-io* "~&GC: ~D bytes consed~%"
          (sb-ext:get-bytes-consed)))

(defun hook-gc (&optional (log-file nil))
  (pushnew 'dribble-gc-info sb-ext:*after-gc-hooks*)
  (when log-file
    (setf (sb-ext:gc-logfile) log-file)))

(defun unhook-gc ()
  (setf sb-ext:*after-gc-hooks*
        (delete 'dribble-gc-info sb-ext:*after-gc-hooks*))
  (if (sb-ext:gc-logfile)
      (prog1 (sb-ext:gc-logfile)
        (setf (sb-ext:gc-logfile) nil))
      nil))

然后(hook-gc "/tmp/x.out") 会告诉你 GC 何时运行以及总共消耗了多少内存,并将大量信息写入/tmp/x.out。这可能至少会让您开始了解正在发生的事情。

另一件可能会有所帮助的事情是在您正在写入的流上插入对force-output 的偶尔调用:可能(但我认为不太可能)正在发生一些奇怪的缓冲导致它关于文件的 lisp 端缓冲区应该有多大的错误决定。

【讨论】:

【参考方案2】:

尝试loop 循环遍历哈希表。

类似的东西:

(loop for k1 
      being the hash-key 
      using (hash-value v1) of (customer-var1 cust-data)
      do (format s "~A ~A~%" k1 v1))

或者如果您不需要这些值:

(loop for k being the hash-key of (customer-var2 cust-data)
      do (format <whatever you need...>))

最初我认为maphash 会收集值,但事实并非如此,正如@tfb 指出的那样。那我就不知道了。

【讨论】:

maphash 不收集值:请参阅CLHS。

以上是关于Lisp - 使用低内存占用写入文件的主要内容,如果未能解决你的问题,请参考以下文章

node 怎么样将一个buffer写入文件

在磁盘读取或写入时ntoskrnl占用cpu,请问如何解决啊,谢谢

openwrt 挂载overlay无法写入

将大型CSV流写入内存中的ZipOutputStream会占用与CSV或潜在zip的大小相同的内存吗?

在帧写入循环中使用和不使用自动释放池的内存占用,为啥?

HDFS知识点