我正在编写代码,以获取pmap
产生的一些惰性结果,并将它们绘制到BufferedImage
上。现在三天来,我一直在试图弄清楚为什么图纸突然开始冻结,并最终停滞了整个过程的1/3。
我终于将其范围缩小到我在另一个线程中循环处理大量数据的事实。
这是我想出的最好的MCVE:
(ns mandelbrot-redo.irrelevant.write-image-mcve
(:import [java.awt.image BufferedImage]
(java.util.concurrent Executors Executor)))
(defn lazy-producer [width height]
(for [y (range height)
x (range width)]
[x y (+ x y)]))
; This works fine; finishing after about 5 seconds when width=5000
(defn sync-consumer [results width height]
(time
(doseq [[i result] (map vector (range) results)]
(when (zero? (rem i 1e6))
(println (str (double (/ i (* width height) 0.01)) "%")))
((fn boop [x] x) result)))) ; Data gets consumed here
; This gets to ~30%, then begins being interupted by 1-4 second lags
(defn async-consumer [results width height]
(doto
(Thread. ^Runnable
(fn []
(sync-consumer results width height)
(println "Done...")))
(.start)))
(defn -main []
(let [width 5000
height (int (* width 2/3))]
(-> (lazy-producer width height)
(async-consumer width height))))
当-main
与sync-consumer
一起运行时,它会在几秒钟后结束。但是,使用async-consumer
时,它会达到25%左右,然后开始变慢,直到最后打印的百分比为30%。如果离开它,我会得到一个OOME。
如果我使用显式的Thread.
,或者在async-consumer
中使用了本地线程池,则它会挂起并崩溃。但是,如果我使用future
,它会像sync-consumer
一样正常。
我得到的唯一提示是,当我在VisualVM中运行此命令时,我看到使用异步版本时Long
的分配失控了:
相比之下,同步版本显示Long
的峰值数量一次约为45mb。
CPU使用率也大不相同:
GC大量增加,但似乎Long
并没有被丢弃。
我可以为此使用future
,但是我被它的异常吞咽行为咬伤了很多次,我很犹豫。
为什么会这样?为什么在新线程中运行它会导致GC疯狂,而同时没有释放数字?
任何人都可以解释这种行为吗?
答案 0 :(得分:2)
同步版本似乎正在处理16M +结果,并且由于本地清除而无法保持结果序列的开头。这意味着在您进行操作时,将创建,处理和GC值。
异步程序在fn中关闭results
并保持头部,将所有16M +值保留在内存中,可能导致GC崩溃吗?
我实际上无法复制您描述的内容-同步和异步对我来说大约需要花费相同的时间,如上所述。 (Clojure 1.9,Java 1.8)。
答案 1 :(得分:0)
我简化了您的示例,并得到不一致的结果。我怀疑手动Thread
对象有时被视为守护线程,因此JVM有时会在完成之前退出:
(def N 5e3)
(def total-count (* N N))
(def report-fact (int (/ total-count 20)))
(defn lazy-producer []
(for [y (range N)
x (range N)]
[x y (+ x y)]))
(defn sync-consumer [results]
(println "sync-consumer: start")
(time
(doseq [[i result] (map vector (range) results)]
(when (zero? (rem i report-fact))
(println (str (Math/round (/ (* 100 i) total-count)) " %")))))
(println "sync-consumer: stop"))
(defn async-consumer [results]
; (spyx (count results))
(spyx (nth results 99))
(let [thread (Thread. (fn []
(println "thread start")
(sync-consumer results)
(println "thread done")
(flush)))]
; (.setDaemon thread false)
(.start thread)
(println "daemon? " (.isDaemon thread))
thread))
(dotest
(println "test - start")
(let [thread (async-consumer
(lazy-producer))]
(when true
(println "test - sleeping")
(Thread/sleep 5000))
; (.join thread)
)
(println "test - end"))
有结果:
----------------------------------
Clojure 1.9.0 Java 10.0.1
----------------------------------
lein test tst.demo.core
test - start
(nth results 99) => [99 0 99]
daemon? false
test - sleeping
thread start
sync-consumer: start
0 %
5 %
10 %
15 %
20 %
25 %
30 %
35 %
40 %
45 %
50 %
55 %
test - end
Ran 2 tests containing 0 assertions.
0 failures, 0 errors.
60 %
lein test 54.58s user 1.37s system 372% cpu 15.028 total
如果我们取消对(.join thread)
行的注释,我们将获得完整的运行:
~/expr/demo > lein test
----------------------------------
Clojure 1.9.0 Java 10.0.1
----------------------------------
lein test tst.demo.core
test - start
(nth results 99) => [99 0 99]
daemon? false
test - sleeping
thread start
sync-consumer: start
0 %
5 %
10 %
15 %
20 %
25 %
30 %
35 %
40 %
45 %
50 %
55 %
60 %
65 %
70 %
75 %
80 %
85 %
90 %
95 %
"Elapsed time: 9388.313828 msecs"
sync-consumer: stop
thread done
test - end
Ran 2 tests containing 0 assertions.
0 failures, 0 errors.
lein test 72.52s user 1.69s system 374% cpu 19.823 total
仿佛Clojure杀死了手动Thread
对象一样,它似乎早已退出。
也许您发现了(间歇性)错误。
答案 2 :(得分:0)
多亏了@amalloy和@Alex,我才能够正常工作。
我在评论中实现了@amalloy的建议,这两种变体在这里和在我的实际情况下都有效:
; Brittle since "once" is apparently more of an implementation detail of the language
(defn async-consumer [results width height]
(doto
(Thread. ^Runnable
(^:once fn* []
(sync-consumer results width height)
(println "Done...")))
(.start)))
; Arguably less brittle under the assumption that if they replace "once" with another mechanism,
; they'll update "delay".
(defn async-consumer [results width height]
(let [d (delay (sync-consumer results width height))]
(doto
(Thread. ^Runnable
(fn []
@d
(println "Done...")))
(.start))))
我还尝试了更新到1.9.0。我认为这可能会解决此问题,因为@Alex说他在1.9.0上,无法重现此问题,there's also this bug fix that seems related.不幸的是,我没有发现任何区别。
如果有一个切实可行的机制,这将很好。 ^:once
看起来不错,但是我不想使用它只是为了以后可能损坏,而delay
的使用似乎只是为了利用对象的内部{{ 1}}。
哦,至少现在可以了。谢谢大家。