簡體   English   中英

向量重新聲明與循環操作中的插入-C ++

[英]Vector re-declaration versus insertions in looping operations - C++

我可以選擇在每次調用func()創建和銷毀向量,並在每次迭代中推送元素,如示例A所示,或者固定初始化,並且在每次迭代中僅覆蓋舊值,如示例B所示。

范例A:

void func () 
{
    std::vector<double> my_vec(5, 0.0);
    for ( int i = 0; i < my_vec.size(); i++) {
        my_vec.push_back(i);
        // do something
    }
}

while (condition) {
    func();
}

范例B:

void func (std::vector<double>& my_vec) 
{
    for ( int i = 0; i < my_vec.size(); i++) {
        my_vec[i] = i;
        // do something
    }
}

while (condition) {
    std::vector<double> my_vec(5, 0.0);
    func(myVec);
}

兩者中的哪一個在計算上不昂貴。 數組的大小不會超過10。

我仍然懷疑所提出的問題不是所要解決的問題,但是我發現答案的重點很可能不會改變。 如果問題得到更新,我總是可以編輯此答案以使其匹配(或者,如果結果不適用,則可以將其刪除)。

取消優化的優先級

有多種因素會影響您編寫代碼的方式。 理想的目標包括空間優化,時間優化,數據封裝,邏輯封裝,可讀性,健壯性和正確的功能。 理想情況下,所有這些目標在每段代碼中都是可以實現的,但這並不是特別現實。 必須犧牲這些目標中的一個或多個,以換取其他目標的情況更有可能發生。 當發生這種情況時,優化通常應該屈服於其他所有方面。

這並不是說優化應該被忽略。 有很多優化很少會阻礙較高優先級的目標。 這些范圍從較小的值(例如通過const引用而不是按值傳遞)到較大的值(例如選擇對數算法而不是指數算法)。 但是,確實會干擾其他目標的優化應推遲到代碼合理地完成並正常運行之后。 此時,應使用探查器確定瓶頸的實際位置。 只有當探查器確認優化已實現目標時,這些瓶頸才是其他目標應達到優化的唯一位置。

對於提出的問題,這意味着主要的關注點不應是計算費用,而應該是封裝。 為什么func()的調用者需要為func()分配空間? 除非分析器將其識別為性能瓶頸,否則不應這樣做。 而且,如果探查器做到了,問問探查器是否對更改有所幫助比問堆棧溢出要容易得多(並且更可靠!)。

為什么?

我可以想到兩個主要原因來取消優化的優先級。 首先,“嗅探測試”是不可靠的。 雖然可能有一些人可以通過查看代碼來識別瓶頸,但還有很多人只是認為自己可以做到。 其次,這就是為什么我們要優化編譯器。 有人想到這個超級聰明的優化技巧只是發現編譯器已經在做這件事並不是聞所未聞的。 保持代碼干凈,讓編譯器處理例行優化。 僅當任務明顯超出編譯器的功能時才介入。

另請參閱:

選擇優化

好的,假設分析器確實將這個由10個元素組成的小數組的構造識別為瓶頸。 下一步是測試替代方法,對嗎? 幾乎。 首先,您需要一個替代方案,我認為對各種替代方案的理論收益進行回顧是有用的。 請記住,這是理論上的,分析器擁有最終決定權。 因此,我將探討該問題中替代方案的優缺點,以及其他一些可能需要考慮的替代方案。 讓我們從最糟糕的選擇開始,逐步走向更好的選擇。

例子A

在示例A中,創建了一個包含5個元素的向量,然后將元素推入向量,直到i達到或超過向量的大小。 看到i和向量的大小如何在每次迭代中都增加一(並且i開始小於該大小),此循環將一直運行,直到向量變大到足以使程序崩潰為止。 這可能意味着數十億次迭代(盡管該問題聲稱其大小不會超過10次)。

輕松實現最昂貴的計算選擇。 不要這樣

例子B

在示例B中,為外部while循環的每次迭代創建一個向量,然后通過引用從func()內部對其進行訪問。 這里的性能缺點包括將參數傳遞給func()並使func()通過引用間接訪問向量。 沒有性能方面的優勢,因為這可以完成基線(請參見下文)所能做的一切,外加一些額外的步驟。

即使編譯器可能能夠彌補缺點,但我認為沒有理由嘗試這種方法。

底線

我使用的基准是對示例A無限循環的修復。 具體而言,將“ my_vec.push_back(i); ”替換為示例B的“ my_vec[i] = i; ”。 這種簡單的方法符合我對探查器進行初始評估的期望。 如果您不能擊敗簡單,那就堅持下去。

示例B *

該問題的案文提出了對示例B的不准確評估。有趣的是,該評估描述了一種可能在基准上有所改進的方法。 要獲取與文本描述匹配的代碼,請將示例B的“ std::vector<double> my_vec(5, 0.0); ”移動到while語句之前的行。 這樣的效果是只構造一次向量,而不是每次迭代都構造向量。

這種方法的缺點與最初編碼的示例B的缺點相同。 但是,我們現在得到了好處,因為向量的構造函數僅被調用一次。 如果構造比間接成本昂貴,則一旦while循環足夠頻繁地迭代,結果應該是凈改進。 (請注意這些條件:這是一個很大的“ if”,並且沒有關於“足夠”的迭代次數的先驗猜測。)嘗試這樣做並查看探查器的內容是合理的。

得到一些靜態

實例B *上有助於保留封裝的一種變體是使用基線(固定的實例A),但在向量聲明之前使用關鍵字static 這帶來了僅構造一次向量的好處,但是沒有與使向量成為參數相關的開銷。 實際上,這樣做的好處可能比示例B *大,因為每個程序執行只進行一次構造,而不是每次while循環啟動時都進行構造。 while循環啟動的次數越多,此好處就越大。

這里的主要缺點是向量將在程序執行期間占據內存。 與示例B *不同,它在包含while循環的塊結束時不會釋放其內存。 在太多地方使用此方法會導致內存膨脹。 因此,盡管對此方法進行分析是合理的,但您可能需要考慮其他選擇。 (當然,如果探查調用做為瓶頸,矮化所有其他的,成本小到足以支付。)

固定大小

我個人對此嘗試哪種優化的選擇是從基線開始,然后將向量切換為std::array<10,double> 我的主要動機是所需的大小不會超過10。另外,與此相關的是,構造double是微不足道的。 數組的構造應與聲明10個double類型的變量相提並論,我希望可以忽略不計。 因此,不需要花哨的優化技巧。 只需讓編譯器執行其操作即可。

這種方法的預期可能的好處是, vector在堆上為其存儲分配空間,這會產生開銷。 本地array將不會有此成本。 但是,這僅僅是可能的好處。 矢量實現可能已經利用了針對小矢量的這種性能考慮。 (也許直到容量需要超過某個魔術數,甚至可能超過10時,它才使用堆。)當我提到“ super-clever”和“ compiler已經在做”的時候,我會回頭再提一下。

我將通過探查器運行它。 如果沒有好處,那么其他方法可能就沒有好處。 當然,請嘗試一下,因為它們很容易,但是最好將其用於其他方面以進行優化。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM