學習Rust(yay!),我試圖理解某些迭代器模式所需的預期慣用編程,同時評分最高性能。注意:不是Rust的Iterator特徵,只是我寫的一個方法,它接受一個閉包,並將它應用於我從磁盤/內存不足中提取的某些數據。Rust中的多個專業化迭代器模式
我很高興地看到,拉斯特(+ LLVM?)坐在我的稀疏矩陣條目編寫一個迭代器,並做稀疏矩陣向量乘法封閉,寫成
iterator.map_edges({ |x, y| dst[y] += src[x] });
和內聯封的生成的代碼中的主體。它進行得非常快。 :D
如果我創建了這些迭代器中的兩個,或者第二次使用第一次(而不是正確性問題),每個實例都會變慢很多(在這種情況下大約是2倍),這可能是因爲優化器不再選擇由於多個呼叫站點而做專門化,並且最終爲每個元素進行函數調用。
我想知道是否有慣用模式,保持愉快的體驗(至少我喜歡它),而不犧牲性能。我的選擇似乎是(無法滿足這個約束):
- 接受狡猾的表現(慢2倍不是致命的,但沒有獎品)。
- 要求用戶提供面向批處理的閉包,以便在迭代器上處理一小批數據。這暴露了迭代器的很多內部元素(數據壓縮得很好,用戶需要知道如何解開它們,或者迭代器需要在內存中展開一個展開的批處理)。
- 使
map_edges
通用於實現假設EdgeMapClosure
特徵的類型,並要求用戶爲他們想要內聯的每個閉包實現此類型。沒有測試過,但我想這會暴露出不同的方法LLVM,其中每個都很好地內聯。缺點是用戶必須編寫自己的封閉(包裝相關狀態等)。 - 可怕的黑客,像做出不同的方法
map_edges0
,map_edges1
,...。或者添加一個通用參數,程序員可以使用這些通用參數來使方法截然不同,但是否則忽略。
非解決方案包括「僅使用for pair in iterator.iter() { /* */ }
」;這是數據/任務並行平臺的準備工作,我希望能夠捕獲/移動這些閉包來工作線程,而不是捕獲主線程的執行。也許我應該使用的模式是寫上述代碼,將它放在一個lambda/closure中,然後將它運回來?
在一個完美的世界中,如果有一個模式會導致源文件中的每個事件map_edges
導致二進制文件中不同的專用方法,而不會強制整個項目在某些可怕的級別進行優化。我出現了與託管語言和JIT的不愉快關係,在這種關係中泛型將成爲實現這一目標的唯一途徑,但Rust和LLVM似乎足夠神奇,所以我認爲這可能是一種好方法。 Rust的迭代器如何處理這個內聯閉包體?或者他們不應該(他們應該!)?
不確定,但您可以嘗試用'#[inline(always)]'標記您的方法。 – Levans 2014-10-10 21:48:38
作爲更新,#[inline(always)]會使性能恢復,但可能是出於「錯誤原因」:由於該方法被內聯到main中,因此閉包也可以隨後被內聯。但是,我不希望將迭代器內聯到main(相反,編寫將閉包作爲參數的庫代碼)。儘管如此,它有幫助,我會看看我是否可以利用它。謝謝! – 2014-10-14 14:58:23
此外,編寫'map_edges(...,extra:T)'的通用方法效果很好,但確實非常不穩定。我需要支付誰來支持'#[monomorphise_for_constant_arguments]'屬性? :d –
2014-10-14 15:00:48