2012-03-02 43 views
2

假設我有一個包含許多小函數的C++代碼,其中每個函數通常都需要一個帶有在運行時已知的n,p的矩陣浮點數M1(n,p),以包含中間計算的結果(不需要初始化M1,只是爲了聲明它,因爲每個函數都會覆蓋M1的所有行)。在C++最佳實踐中破壞矩陣?

部分原因是每個函數都在原始數據矩陣上工作,所以無法修改,所以需要在「別處」完成許多操作(排序,去含義,sphering)。

更好的做法是在每個函數中創建一個臨時M1(n,p),或者在main()中創建一個臨時M1(n,p),並將其作爲每種函數可以使用的一種存儲桶傳遞給每個函數廢品空間?

對於n,n和p經常是中等大的[10^2-10^4],對於p是[5-100]。

(最初發布在codereview stackexchange但在此移動)。

最佳,

+0

取決於。內存分配很昂貴。從本地的分配開始,並修改如果分配太昂貴 – Anycorn 2012-03-02 07:41:57

+1

@Anycorn內存分配可能比訪問500或更多的值更便宜,並且肯定比訪問一百萬個值更便宜。 – 2012-03-02 09:05:11

+0

@JamesKanze +1與這些操作相比,堆分配的相對效率通常是微不足道的。我正在考慮編輯我的文章,以避免完全建議優化路線。 – stinky472 2012-03-02 09:35:36

回答

2
  1. 堆分配的確的確相當昂貴。
  2. 不成熟的優化是不好的,但如果你的圖書館是相當一般和矩陣是巨大的,尋求一個有效的設計可能並不成熟。畢竟,在你積累了許多依賴之後,你不想修改你的設計。
  3. 有各種層次可以解決這個問題。例如,您可以通過在內存分配程度級別解決堆分配開銷(例如,每個線程內存池),而堆分配很昂貴,您正在創建一個巨型矩陣來執行一些相當昂貴的操作在矩陣上(通常線性複雜或更差)。相對而言,在免費商店中分配一個矩陣可能並不是那麼昂貴,相比之下你隨後不可避免地要做的事情,因此與類似於排序的函數的整體邏輯相比,它實際上可能相當便宜。

我建議你自然地寫代碼,考慮#3作爲未來的可能性。也就是說,不要參考用於中間計算的矩陣緩衝區來加速創建臨時對象。製作臨時表格並按價值歸還。正確性和良好,清晰的接口是第一位的。

晴這裏的目標是分離的矩陣(通過分配器或其他方式)的造物政策,爲您提供了喘息的空間,以優化作爲一種​​事後不改變太多現有的代碼。如果你可以通過修改所涉及功能的實現細節來做到這一點,或者更好的做法是隻修改你的矩陣類的實現,那麼你真的很好,因爲那樣你就可以自由地在不改變設計的情況下進行優化,並且從效率的觀點來看,任何設計都可以完成。


警告:以下內容僅適用於如果您真的想要充分利用每個週期。理解#4並且讓自己成爲一名優秀的分析師是至關重要的。還值得注意的是,通過優化這些矩陣算法的內存訪問模式,您可能會做得更好,而不是試圖優化堆分配。


如果您需要優化內存分配,請考慮使用像每個線程內存池一般的東西對其進行優化。例如,你可以讓你的矩陣採用可選的分配器,但是我強調這裏是可選的,我也會首先用一個簡單的分配器實現強調正確性。

換句話說:

是更好的做法是每個函數內聲明M1(N,P),或 而一勞永逸在main(),並把它傳遞給每一個被用作 一種桶,每個功能可以用作廢品空間。

繼續創建M1在每個功能的暫時的。儘量避免要求客戶製作一些對他/她沒有意義的矩陣來計算中間結果。這將暴露一個優化細節,這是我們在設計接口時應該努力不去做的事情(隱藏客戶端不應該知道的所有細節)。

相反,着眼於更廣泛的概念,如果你絕對要該選項,以加速這些臨時對象的創建,如可選的分配器。這與實用的設計適合於像std::set

std::set<int, std::less<int>, MyFastAllocator<int>> s; // <-- okay 

即使大多數人只是做:

std::set<int> s; 

在你的情況,這可能僅僅是: M1 my_matrix(N,P,分配) ;

這是一個微妙的區別,但是分配器是一個更爲一般的概念,我們可以使用緩存矩陣,否則對客戶端沒有意義,除非它是某種緩存,您的函數需要它們來幫助他們更快地計算結果。請注意,它不必是一般的分配器。它可能只是你預先分配的矩陣緩衝區傳遞給矩陣構造函數,但從概念上講,它可能是很好的分離出來,僅僅是因爲它對客戶端來說有點不透明。

此外,構建此臨時矩陣對象還需要注意不要跨線程共享它。這是您可能想要將這個概念推廣到一點的另一個原因,因爲像矩陣分配器這樣的更通用的東西可以考慮線程安全性,或者至少通過設計強調一個單獨的分配器應該更多每個線程都可以創建,但原始矩陣對象可能不能。


以上僅在您真正關心接口質量時纔有用。如果不是的話,我建議你用Matthieu的建議,因爲它比創建分配器簡單得多,但我們都強調使加速版本可選

+1

大家總是認爲他們可以擊敗標準分配器。你不認爲std :: allocator的實現者會想到這個嗎? – CashCow 2012-03-02 08:28:43

+0

@CashCow標準的分配器非常一般。例如,它假定我們立即釋放分配的塊。我實際上做了一個簡單的基於堆棧的內存池分配器。它以平均4個週期分配內存,而標準分配器在我們的平臺上需要大約400個(通常情況下是增加一個指針)。我的特殊技巧:我不釋放內存釋放!通常情況下,這是一個泄漏事件,除了分配器上有清除功能之外..但這會使其使用起來很危險,我們只在我們的raytracer的性能關鍵部分中使用它。 – stinky472 2012-03-02 08:35:34

+1

@CashCow所以要匹配標準分配器的通用性並且剔除其效率,我會說這確實是不切實際的目標。但是如果我們在降低普遍性,安全性或碎片化的風險方面做出一些假設,那麼我們可以很容易地超過它的速度,但是對於這些區域中的一個或多個來說有一定的代價。 – stinky472 2012-03-02 08:37:15

1

首先嚐試定義函數內部的矩陣。這絕對是更好的設計選擇。但是如果你的性能損失你不能妥協,我認爲只要你記住這些函數不再是線程安全的,那麼「每個引用的緩衝區通過」就可以了。如果在任何時候使用線程,每個線程都需要它自己的緩衝區。

2

不要使用過早優化。創造一些能正常工作的東西,如果顯示速度很慢,可以稍後進行優化。

(順便說一下,我不認爲stackoverflow是它的正確的地方)。

實際上,如果您想加快在大型矩陣上運行的應用程序,那麼使用併發將是您的解決方案。如果你使用併發性,如果你有一個大的全局矩陣,你可能會遇到更多的麻煩。

從本質上講,它意味着即使您擁有內存,您也永遠不會有多次計算髮生。

矩陣的設計需要最佳。我們不得不看這個設計。

因此,我通常會在你的代碼中說,不,不要創建一個大的全局矩陣,因爲它聽起來不對,你想用它做什麼。

+0

:>感謝您的建議。在這種情況下,主函數必須在大量的獨立輸入矩陣X_ {1} ... X_ {k}上重複,因此併發性在比函數「更高」寫作,這是爲了這些X_ {k}中的一個[我希望它很清楚 - 如果不讓我知道,我將其重述)。 – user189035 2012-03-02 09:30:29

+1

當然,你可以通過讓你的進程的多個實例運行來實現併發性,只是你不應該通過給自己一個在錯誤區域優化的設計來排除它在一個進程內。 – CashCow 2012-03-02 09:52:22

+0

是的,我明白你的觀點,而且它通常是完全有效的。我想提供一些具體情況的具體信息。 – user189035 2012-03-02 10:09:53

1

在需要外部提供的緩衝區時,性能方面有很多優勢,特別是在需要鏈接使用它的函數時。

但是,從用戶的角度來看,它很快就會變得煩人。

我常常發現,這是很簡單的在C++中通過簡單地offerring兩種方式來獲得兩全其美:

int compute(Matrix const& argument, Matrix& buffer); 

inline int compute(Matrix const& argument) { 
    Matrix buffer(argument.width, argument.height); 
    return compute(argument, buffer); 
} 

這個非常簡單的包裝意味着代碼編寫一次,兩個略有不同介紹接口。

更多地參與API(服用buffer)也略少安全爲buffer必須尊重一些尺寸的限制WRT的說法,所以你可能要進一步隔離快速 API(例如命名空間的後面),以鼓勵用戶首先使用速度更慢但更安全的界面,並且只有在證明有必要時才嘗試快速界面。