在Clojure中获得意外的序列实现
在尝试对复杂计算进行故障排除时注意到一些奇怪的情况。我有一个奇怪的错误,所以我开始逐步建立计算,很早就发现一个非常长的seq被意外地实现了。我有一系列的列表,如下所示:在Clojure中获得意外的序列实现,clojure,jvm,out-of-memory,Clojure,Jvm,Out Of Memory,在尝试对复杂计算进行故障排除时注意到一些奇怪的情况。我有一个奇怪的错误,所以我开始逐步建立计算,很早就发现一个非常长的seq被意外地实现了。我有一系列的列表,如下所示: (0 1 2 3) (0 1 2 4) (0 1 2 5) 对于n-choose-k,对于n=143和k=4,有点别出心裁地命名为“combos”。我计划将其作为一个seq进行操作,以保持内存消耗合理,但这失败了: (def combos (combinations 4 143)) (def semantically-also
(0 1 2 3)
(0 1 2 4)
(0 1 2 5)
对于n-choose-k,对于n=143和k=4,有点别出心裁地命名为“combos”。我计划将其作为一个seq进行操作,以保持内存消耗合理,但这失败了:
(def combos (combinations 4 143))
(def semantically-also-combos
(filter nil? (map identity combos)))
;; this prints instantly and uses almost no memory, as expected
(println (first combos)) ; prints (0 1 2 3)
;; this takes minutes and runs the JVM out of memory
;; without printing anything
(println (first semantically-also-combos))
根据type
的说法,它们都是clojure.lang.LazySeq
,但其中一个按预期工作,另一个使流程崩溃。为什么整个seq只需通过identity函数运行并检查它是否为nil就可以实现
要复制的完整代码
我对示例进行了如下修改:
(ns tst.demo.core
(:use tupelo.core tupelo.test))
(def cnt (atom 0))
; Copied from rosetta code
(defn combinations
"If m=1, generate a nested list of numbers [0,n)
If m>1, for each x in [0,n), and for each list in the recursion on [x+1,n), cons the two"
[m n]
(let [comb-aux (fn comb-aux
[m start]
(swap! cnt inc)
(when (zero? (mod @cnt 100))
(print \.)
(flush))
(when (zero? (mod @cnt 10000))
(newline)
(print @cnt " "))
(if (= 1 m)
(for [x (range start n)]
(list x))
(for [x (range start n)
xs (comb-aux (dec m) (inc x))]
(cons x xs))))]
(comb-aux m 0)))
(println "Generating combinations...")
(def combos (combinations 4 143))
(def should-also-be-combos
(filter nil? (map identity combos)))
以及调用:
(dotest
(newline)
(spyx (type combos))
(spyx (type should-also-be-combos))
(newline)
(println :1)
(println (first combos))
(newline)
(println :2)
(println (first should-also-be-combos))
(newline))
结果:
-------------------------------
Clojure 1.10.2 Java 15
-------------------------------
Testing tst.demo.core
(type combos) => clojure.lang.LazySeq
(type should-also-be-combos) => clojure.lang.LazySeq
:1
(0 1 2 3)
:2
....................................................................................................
10000 ....................................................................................................
20000 ....................................................................................................
30000 ....................................................................................................
40000 ....................................................................................................
50000 ....................................................................................................
60000 ....................................................................................................
70000 ....................................................................................................
80000 ....................................................................................................
90000 ....................................................................................................
100000 ....................................................................................................
110000 ....................................................................................................
120000 ....................................................................................................
130000 ....................................................................................................
140000 ....................................................................................................
150000 ....................................................................................................
160000 ....................................................................................................
170000 ....................................................................................................
180000 ....................................................................................................
190000 ....................................................................................................
200000 ....................................................................................................
210000 ....................................................................................................
220000 ....................................................................................................
230000 ....................................................................................................
240000 ....................................................................................................
250000 ....................................................................................................
260000 ....................................................................................................
270000 ....................................................................................................
280000 ....................................................................................................
290000 ....................................................................................................
300000 ....................................................................................................
310000 ....................................................................................................
320000 ....................................................................................................
330000 ....................................................................................................
340000 ....................................................................................................
350000 ....................................................................................................
360000 ....................................................................................................
370000 ....................................................................................................
380000 ....................................................................................................
390000 ....................................................................................................
400000 ....................................................................................................
410000 ....................................................................................................
420000 ....................................................................................................
430000 ....................................................................................................
440000 ....................................................................................................
450000 ....................................................................................................
460000 ....................................................................................................
470000 ....................................................................................................
480000 ..........................................................................nil
在我的台式计算机上计时(5年前,8核,32Gb RAM):
你可以看到这个函数被调用了大约1/2百万次,在我的电脑上需要5.3秒
我相信您正在看到类似于中描述的内容。它的递归形式也让我想起了著名的例子,这是一个看似无害的函数如何“爆炸”的例子,即使输入值很小。我对这个例子做了如下修改:
(ns tst.demo.core
(:use tupelo.core tupelo.test))
(def cnt (atom 0))
; Copied from rosetta code
(defn combinations
"If m=1, generate a nested list of numbers [0,n)
If m>1, for each x in [0,n), and for each list in the recursion on [x+1,n), cons the two"
[m n]
(let [comb-aux (fn comb-aux
[m start]
(swap! cnt inc)
(when (zero? (mod @cnt 100))
(print \.)
(flush))
(when (zero? (mod @cnt 10000))
(newline)
(print @cnt " "))
(if (= 1 m)
(for [x (range start n)]
(list x))
(for [x (range start n)
xs (comb-aux (dec m) (inc x))]
(cons x xs))))]
(comb-aux m 0)))
(println "Generating combinations...")
(def combos (combinations 4 143))
(def should-also-be-combos
(filter nil? (map identity combos)))
以及调用:
(dotest
(newline)
(spyx (type combos))
(spyx (type should-also-be-combos))
(newline)
(println :1)
(println (first combos))
(newline)
(println :2)
(println (first should-also-be-combos))
(newline))
结果:
-------------------------------
Clojure 1.10.2 Java 15
-------------------------------
Testing tst.demo.core
(type combos) => clojure.lang.LazySeq
(type should-also-be-combos) => clojure.lang.LazySeq
:1
(0 1 2 3)
:2
....................................................................................................
10000 ....................................................................................................
20000 ....................................................................................................
30000 ....................................................................................................
40000 ....................................................................................................
50000 ....................................................................................................
60000 ....................................................................................................
70000 ....................................................................................................
80000 ....................................................................................................
90000 ....................................................................................................
100000 ....................................................................................................
110000 ....................................................................................................
120000 ....................................................................................................
130000 ....................................................................................................
140000 ....................................................................................................
150000 ....................................................................................................
160000 ....................................................................................................
170000 ....................................................................................................
180000 ....................................................................................................
190000 ....................................................................................................
200000 ....................................................................................................
210000 ....................................................................................................
220000 ....................................................................................................
230000 ....................................................................................................
240000 ....................................................................................................
250000 ....................................................................................................
260000 ....................................................................................................
270000 ....................................................................................................
280000 ....................................................................................................
290000 ....................................................................................................
300000 ....................................................................................................
310000 ....................................................................................................
320000 ....................................................................................................
330000 ....................................................................................................
340000 ....................................................................................................
350000 ....................................................................................................
360000 ....................................................................................................
370000 ....................................................................................................
380000 ....................................................................................................
390000 ....................................................................................................
400000 ....................................................................................................
410000 ....................................................................................................
420000 ....................................................................................................
430000 ....................................................................................................
440000 ....................................................................................................
450000 ....................................................................................................
460000 ....................................................................................................
470000 ....................................................................................................
480000 ..........................................................................nil
在我的台式计算机上计时(5年前,8核,32Gb RAM):
你可以看到这个函数被调用了大约1/2百万次,在我的电脑上需要5.3秒
我相信您正在看到类似于中描述的内容。它的递归形式也让我想起了著名的例子,这是一个看似无害的函数如何“爆炸”的例子,即使输入值很小。为什么不检查
(filter nil?(map identity combos))
是否实际返回了传递给组合的更小参数所期望的结果?我做到了。这是组合
返回的参数2和5:
(combinations 2 5)
;; => ((0 1) (0 2) (0 3) (0 4) (1 2) (1 3) (1 4) (2 3) (2 4) (3 4))
这是我们通过示例中的额外延迟序列操作得到的结果:
(filter nil? (map identity (combinations 2 5)))
;; => ()
(filter nil?…)
所做的是保留满足谓词的所有元素。并且输入序列中没有一个元素是nil?
,因此它将扫描整个输入序列,并且不会找到任何元素。也许您想使用(remove nil?…)
,这将删除满足谓词的元素
(remove nil? (map identity (combinations 2 5)))
;; => ((0 1) (0 2) (0 3) (0 4) (1 2) (1 3) (1 4) (2 3) (2 4) (3 4))
回到原始示例,这是我使用remove
得到的结果:
(first (combinations 4 143))
;; => (0 1 2 3)
(first (remove nil? (map identity (combinations 4 143))))
;; => (0 1 2 3)
我的一般建议是先用“较小”的数据(如2和5)测试函数,然后再使用“较大”的数据(如4和143)。为什么不检查(filter nil?(map identity combos))
是否实际返回传递给组合的更小参数的结果?我做到了。这是组合
返回的参数2和5:
(combinations 2 5)
;; => ((0 1) (0 2) (0 3) (0 4) (1 2) (1 3) (1 4) (2 3) (2 4) (3 4))
这是我们通过示例中的额外延迟序列操作得到的结果:
(filter nil? (map identity (combinations 2 5)))
;; => ()
(filter nil?…)
所做的是保留满足谓词的所有元素。并且输入序列中没有一个元素是nil?
,因此它将扫描整个输入序列,并且不会找到任何元素。也许您想使用(remove nil?…)
,这将删除满足谓词的元素
(remove nil? (map identity (combinations 2 5)))
;; => ((0 1) (0 2) (0 3) (0 4) (1 2) (1 3) (1 4) (2 3) (2 4) (3 4))
回到原始示例,这是我使用remove
得到的结果:
(first (combinations 4 143))
;; => (0 1 2 3)
(first (remove nil? (map identity (combinations 4 143))))
;; => (0 1 2 3)
我的一般建议是,先用“较小”的数据(如2和5)测试你的功能,然后再使用“较大”的数据(如4和143)。是的,它在大约60秒的时间内耗尽了我微薄的macbook air的内存。那篇文章似乎是一个很好的开始,谢谢!是的,它在大约60秒内就把我那微不足道的macbook air的内存用完了。那篇文章似乎是一个很好的开始,谢谢!你是对的,通缉(不是(nil?x))
,愚蠢的错误。在REPL中尝试东西有助于发现这些错误。你是对的,通缉(不是(nil?x))
,愚蠢的错误。在REPL中尝试东西有助于发现这些错误。