在Reducers的許多資源(如the canonical blog post by Rich Hickey)中,聲稱reducer比常規的收集函數((map ... (filter ...))
等)要快,因爲開銷較少。爲什麼Clojure的core.reducers比懶惰的收集函數更快
什麼是避免的額外開銷? IIUC甚至懶惰的收集功能最終只走一次原始序列。計算中間結果的細節有何不同?
指針到Clojure中的實現,有助於理解上的差異將是最有幫助的太
在Reducers的許多資源(如the canonical blog post by Rich Hickey)中,聲稱reducer比常規的收集函數((map ... (filter ...))
等)要快,因爲開銷較少。爲什麼Clojure的core.reducers比懶惰的收集函數更快
什麼是避免的額外開銷? IIUC甚至懶惰的收集功能最終只走一次原始序列。計算中間結果的細節有何不同?
指針到Clojure中的實現,有助於理解上的差異將是最有幫助的太
我認爲一個重要觀點是,從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。
相關的地方「注意,有沒有產生中間集合。」
這是改進,因爲垃圾收集器的壓力比較小。
當你結合像map
,filter
這樣的操作時,那些函數迭代一個集合並返回一個新的集合,然後傳遞給下一個函數。減速器情況並非如此。
因此,它們可以用作那些功能(也就是說,它們以相同的方式組成)並且可以應用於相同的集合。但是增加了性能。
具有和不減速的地圖/地圖/過濾操作的粗糙和完全不科學草圖如下:
沒有減速器
初始集合=>地圖=>中介集合=>地圖=> intermediary collection => filter => final collection
沒有reducer(即clojure.core map/filter函數)int中間集合是懶惰生產的。 也就是說,只有在下一個處理步驟需要時才產生新的元素,一次一個元素或一個塊。
注意:塊是由32個元素組成的塊(儘管這應該被視爲實現細節)。這是出於效率的原因,以避免從一次計算跳到另一次計算。
查看map
函數return value例如:它返回lazy-seq
(transducers的情況除外)。
隨着減速
初步收集=>地圖/地圖/過濾器減速=>最終收集
如果您需要處理整個集合快,那麼懶惰也是在越來越表現方式。刪除懶惰seqs的中間層提供了性能增益。 Reducers可以做到這一點並且可以快速處理收集,因此速度更快。您還可以保持相同的功能來緩解切換。
請仔細閱讀下面的意見,尤其是來自@米哈爾Marczyk
我不相信這是真的。 Clojure中的集合是懶惰的,'map' et.al堅持懶惰 - 當疊加'map','reduce','filter'等時,只有一次通過輸入集合,而不是多次。參見http:///clojure.org/reference/sequences – zlatanski
在最初的收集是。但是,他們創建了另一個集合,迭代(僅一次),然後傳遞給下一個函數,該函數遍歷新集合(僅再次)...等等。換能器沒有迭代的中間集合。我編輯了我的答案,希望它有幫助。 – nha
如果你說的是真的,那麼'(以3(地圖增量(甚至過濾?(範圍999999999))))'將花費很長時間並使用大量內存。但它需要30個用戶,並且幾乎不消耗內存 – zlatanski
謝謝 - 這聽起來比我的其他答案更正確。另一個答案意味着整個seq是在每一步之間實現的,這是不對的。這種分配成本是我理解的東西更有意義,而這是我們不想支付的成本,這是減速器更快的成本。 – zlatanski
我想你可以通過這種方式閱讀第一個答案......我認爲這可能是一個意外的閱讀,但如果它強烈地向你表明,那麼肯定的是,這是誤導。惰性seq處理一次髮生一個元素或一個塊(最多32個元素的塊),並在需要時實現新元素或塊。我認爲原始答案試圖做出的正確觀點是,當你將懶惰的seq轉換進行堆棧時,每一層都需要分配它自己的懶惰seq對象,每個元素或塊有一個「單元」(儘管它們當然會被懶惰地實現)。 –
@MichałMarczyk:第一個答案說:「當你結合像map這樣的操作,過濾那些函數遍歷一個集合,並返回一個新的集合,然後傳遞給下一個函數**」,這聽起來毫不含糊。 nha還一直在評論中捍衛這一點,所以我放棄了爭論。後來看來,他/她修補了更廣泛的答案。 – zlatanski