2016-03-15 46 views

回答

3

我認爲一個重要觀點是,從original blog post下面這段話的一個:

(require '[clojure.core.reducers :as r]) 
(reduce + (r/filter even? (r/map inc [1 1 1 2]))) 
;=> 6 

這應該很熟悉 - 它是相同的命名函數,以相同的順序應用,具有相同的參數,產生與Clojure基於seq的fns相同的結果。不同之處在於,減少渴望,並且這些減速器fns不在seq遊戲中,沒有每步分配開銷,因此速度更快。當你需要時,懶惰是偉大的,但是當你不需要時,你不應該爲此付出代價。

實現懶惰序列的帶有一個(線性)分配成本:每從懶惰SEQ另一元件被實現,帶seq的其餘被存儲在新的thunk時間,以及表示這樣的'thunk'是a new clojure.lang.LazySeq object

我相信這些LazySeq對象是引用中引用的分配開銷。對於減速器,沒有逐步實現惰性seq元素,因此根本沒有實例化LazySeq thunk。

+0

謝謝 - 這聽起來比我的其他答案更正確。另一個答案意味着整個seq是在每一步之間實現的,這是不對的。這種分配成本是我理解的東西更有意義,而這是我們不想支付的成本,這是減速器更快的成本。 – zlatanski

+1

我想你可以通過這種方式閱讀第一個答案......我認爲這可能是一個意外的閱讀,但如果它強烈地向你表明,那麼肯定的是,這是誤導。惰性seq處理一次髮生一個元素或一個塊(最多32個元素的塊),並在需要時實現新元素或塊。我認爲原始答案試圖做出的正確觀點是,當你將懶惰的seq轉換進行堆棧時,每一層都需要分配它自己的懶惰seq對象,每個元素或塊有一個「單元」(儘管它們當然會被懶惰地實現)。 –

+0

@MichałMarczyk:第一個答案說:「當你結合像map這樣的操作,過濾那些函數遍歷一個集合,並返回一個新的集合,然後傳遞給下一個函數**」,這聽起來毫不含糊。 nha還一直在評論中捍衛這一點,所以我放棄了爭論。後來看來,他/她修補了更廣泛的答案。 – zlatanski

5

相關的地方「注意,有沒有產生中間集合。」

這是改進,因爲垃圾收集器的壓力比較小。

當你結合像map,filter這樣的操作時,那些函數迭代一個集合並返回一個新的集合,然後傳遞給下一個函數。減速器情況並非如此。

因此,它們可以用作那些功能(也就是說,它們以相同的方式組成)並且可以應用於相同的集合。但是增加了性能。

具有和不減速的地圖/地圖/過濾操作的粗糙和完全不科學草圖如下:

沒有減速器

初始集合=>地圖=>中介集合=>地圖=> intermediary collection => filter => final collection

沒有reducer(即clojure.core map/filter函數)int中間集合是懶惰生產的。 也就是說,只有在下一個處理步驟需要時才產生新的元素,一次一個元素或一個塊。

注意:塊是由32個元素組成的塊(儘管這應該被視爲實現細節)。這是出於效率的原因,以避免從一次計算跳到另一次計算。

查看map函數return value例如:它返回lazy-seqtransducers的情況除外)。

隨着減速

初步收集=>地圖/地圖/過濾器減速=>最終收集

如果您需要處理整個集合快,那麼懶惰也是在越來越表現方式。刪除懶惰seqs的中間層提供了性能增益。 Reducers可以做到這一點並且可以快速處理收集,因此速度更快。您還可以保持相同的功能來緩解切換。

請仔細閱讀下面的意見,尤其是來自@米哈爾Marczyk

+0

我不相信這是真的。 Clojure中的集合是懶惰的,'map' et.al堅持懶惰 - 當疊加'map','reduce','filter'等時,只有一次通過輸入集合,而不是多次。參見http:///clojure.org/reference/sequences – zlatanski

+2

在最初的收集是。但是,他們創建了另一個集合,迭代(僅一次),然後傳遞給下一個函數,該函數遍歷新集合(僅再次)...等等。換能器沒有迭代的中間集合。我編輯了我的答案,希望它有幫助。 – nha

+0

如果你說的是真的,那麼'(以3(地圖增量(甚至過濾?(範圍999999999))))'將花費很長時間並使用大量內存。但它需要30個用戶,並且幾乎不消耗內存 – zlatanski