Clojure:如何从内存中删除对象?

时间:2016-04-25 10:08:40

标签: clojure

我刚刚完成一个程序,它基本上收集数据并计算几个表(通常由过滤器或聚合级别(我写了一个SELECT + GROUP BY,如Clojure函数),我最终用来计算汇总表。这个汇总表以后使用,只有30 000行。

为了让你明白,这是我必须启动的命令:

(def summary-pages (merge-summary-pages (merge-pages-stock (compute-stock) (compute-pages) (compute-xx-prices) (compute-g) (compute-brands) (compute-prices) (compute-sales) (compute-sales-month))
                                        (merge-full-pages-per-page (merge-full-pages (merge-pages-stock (compute-stock) (compute-pages) (compute-xx-prices) (compute-g) (compute-brands) (compute-prices) (compute-sales) (compute-sales-month)) (merge-pages-stock-excluded (compute-pages) (compute-stock) (compute-g) (compute-brands) (compute-prices) (compute-sales) (compute-sales-month))))
                                        (merge-pages-stock-per-page (merge-pages-stock (compute-stock) (compute-pages) (compute-xx-prices) (compute-g) (compute-brands) (compute-prices) (compute-sales) (compute-sales-month)))
                                        (merge-affectations-count (compute-affectations)))) 

如您所见,我会多次调用相同的数据(实际上(compute-pages)调用compute-affectations

这样可行,但问题是compute-pages,特别是compute-affectations在Google BQ(1500万行)和microsoft sql server(45百万行)上都是非常庞大的查询。

问题是,查询它们需要花费时间4-5次,而且我也害怕打破数据库 另一个问题是我必须计算所有compute-affectations因为它来自sql server而我的左连接使用group-by。

我尝试用def拆分作业但是我有GC开销错误。 因为我可以在计算后清理affectations,我试过

(def affectations nil)

...不会改变任何内容,我在microsoft命令面板中看不到内存空闲。

有没有办法清理记忆?

在Python中我的程序完全没有问题(实际上内存使用率最多为80%)但是这里的堆空间为13gb,当我对任何子数据使用def时,我总是遇到问题。 我有16GB的ram所以我不能增加堆空间,而且我觉得奇怪的是这样的“小”数据占用了这么多内存。

我用csv计算测试数据,基础数据只有3.3gb ......

编辑

工作代码(部分内容):

            (let [_ (init-config! path)
                 _ (write-affectations)
                 _ (write-pages)
                 _ (spit "prepared-pages.edn" (prn-str (prepare-pages (read-pages))) :append :true)
                 _ (write-stock)
                 _ (write-prices)
                 _ (write-xx-prices)
                 _ (write-brands)
                 _ (write-g)
                 _ (write-vehicles)
                 _ (write-sales)
                 _ (write-sales-month)
                 _ (System/gc)
                stock (read-stock)
                affectations (read-affectations)
                pages (read-pages)
                prepared-pages (prepare-pages pages)
                xx-prices (read-xx-prices)
                g (read-g)
                brands (read-brands)
                prices (read-prices)
                sales (read-sales)
                sales-month (read-sales-month)
                pages-stock (merge-pages-stock stock prepared-pages xx-prices g brands prices sales sales-month)
                pages-stock-excluded (merge-pages-stock-excluded prepared-pages stock g brands prices sales sales-month)
                full-pages-per-page (-> (merge-full-pages pages-stock pages-stock-excluded)
                                        (merge-full-pages-per-page))
                pages-stock-per-page (merge-pages-stock-per-page pages-stock)
                affectations-count (merge-affectations-count affectations)
                summary-pages (doall (merge-summary-pages pages-stock full-pages-per-page pages-stock-per-page affectations-count))
                _ (System/gc)
                _ (io/delete-file "affectations.edn")
                _ (io/delete-file "pages.edn")
                _ (io/delete-file "prepared-pages.edn")
                _ (io/delete-file "stock.edn")
                _ (io/delete-file "prices.edn")
                _ (io/delete-file "xx-prices.edn")
                _ (io/delete-file "brands.edn")
                _ (io/delete-file "g.edn")
                _ (io/delete-file "vehicles.edn")
                _ (io/delete-file "sales.edn")
                _ (io/delete-file "sales-month.edn")

我在HDD(.edn文件)上写了查询内容,然后我懒洋洋地读它并将其传递给函数。

谢谢!

1 个答案:

答案 0 :(得分:2)

如果不确切知道这些是什么,很难检查以下情况:

  • 抓住一个巨大的懒惰序列的头部
  • lazy bugs(代码在读取延迟的结果序列之前关闭数据库连接)
  • 在内存中保存相同内容的太多副本。

最后一个显然是你问题的一部分(或者也许是全部)。我重复了你的例子,所以每个值只计算一次。

(def summary-pages
  (let [stock (compute-stock)
        pages (compute-pages)
        prices (compute-prices)
        brands (compute-brands)
        sales-month (compute-sales-month)
        g (compute-g)
        sales (compute-sales)
        xx-prices (compute-xx-prices)
        pages-stock (merge-pages-stock stock pages xx-prices g brands prices sales sales-month)
        pages-stock-excluded (merge-pages-stock-excluded pages stock g brands prices sales sales-month)
        full-pages-per-page (merge-full-pages-per-page (merge-full-pages pages-stock pages-stock-excluded))
        pages-stock-per-page (merge-pages-stock-per-page pages-stock)
        affectations-count (merge-affectations-count (compute-affectations))]
       (merge-summary-pages pages-stock 
                            full-pages-per-page
                            pages-stock-per-page
                            affectations-count))) 

您的下一步是注释掉除第一个之外的所有内容,确认它在合理的时间内正确运行。然后取消注释下一个并重复。

<小时/> 由于您的数据很大,并且您希望您的代码一次使用延迟序列来处理它的窗口,我想向您介绍如何&#34;本地绑定&#34;和#34;当地人清理&#34;在Clojure工作。

这是一个&#34;应该&#34;的序列的例子。通过按住十亿个整数序列的头来导致内存不足:

user> (time
       (let [huge-sequence (range 1e9)]
         (last huge-sequence)))
"Elapsed time: 49406.048091 msecs"
999999999

但它没有?为什么?

它必须以某种方式弄清楚它实际上并不需要存储巨大序列的头部,因为之后没有人会使用它!因此,让我们通过更改示例来测试它必须保持头部并查看它是否仍然有效

user> (time
       (let [huge-sequence (range 1e9)]
         (last huge-sequence)
         (first huge-sequence)))

这次我的CPU风扇启动,所有8个处理器立即旋转到100%。五分钟后,我开始变得不耐烦了,所以我会去吃午饭,看看我吃饭的时候是否吃完了。到目前为止,我们的假设看起来很准确。

因此,当您在一个大的let块中定义一堆序列然后进行一次使用它们的函数调用时,并且在该调用之后从不接触它们,序列的头部通常可以是在功能调用时清除,它将会#34;神奇地&#34;工作。这就是我建议你从上到下一次测试这些的原因。

几乎在Clojure中从来没有理由两次定义相同的数据,我们在这里认真考虑参考透明度。

<小时/> 更新:测试最终耗尽内存,虽然时间调用被抛出的异常所绕过,所以我不知道实际花了多长时间:

user> (time
       (let [huge-sequence (range 1e9)]
         (last huge-sequence)
         (first huge-sequence)))
OutOfMemoryError Java heap space  [trace missing]