簡體   English   中英

哪個更有效:返回值與通過引用傳遞?

[英]Which is more efficient: Return a value vs. Pass by reference?

我目前正在研究如何編寫有效的C ++代碼,並且在函數調用方面,我想到了一個問題。 比較此偽代碼功能:

not-void function-name () {
    do-something
    return value;
}
int main () {
    ...
    arg = function-name();
    ...
}

使用以下否則相同的偽代碼功能:

void function-name (not-void& arg) {
    do-something
    arg = value;
}
int main () {
    ...
    function-name(arg);
    ...
}

哪個版本更有效,在哪個方面(時間,內存等)更有效? 如果取決於,那么什么時候第一個會更有效率,什么時候第二個會更有效率?

編輯 :就上下文而言,此問題僅限於與硬件平台無關的差異,並且大多數情況下也限於軟件。 是否存在與機器無關的性能差異?

編輯 :我看不出這是重復的。 另一個問題是比較按引用傳遞(上一個代碼)與按值傳遞(下一個):

not-void function-name (not-void arg)

這和我的問題不同。 我的重點不是哪個是將參數傳遞給函數的更好方法。 我關注的是這是更好的方式來結果來自外部的范圍傳遞一個變量。

首先,要考慮到返回對象總是比通過引用傳遞對象更具可讀性(並且在性能上非常相似),因此對於您的項目來說,返回對象並提高可讀性而又不存在重大的性能差異可能會更有趣。 。 如果您想知道如何以最低的價格買東西,那您需要返回什么:

  1. 如果需要返回一個簡單或基本對象,則兩種情況下的性能都將相似。

  2. 如果對象太大而又復雜,則返回該對象將需要一個副本,它可能比將其作為引用參數要慢,但我認為它將花費更少的內存。

無論如何,您都必須考慮編譯器進行了大量的優化,這使得兩種性能都非常相似。 請參閱復制清除

好吧,必須理解編譯不是一件容易的事。 編譯器編譯您的代碼時要考慮很多因素。

一個人不能簡單地回答這個問題,因為C ++標准沒有提供標准的ABI(抽象二進制接口),因此每個編譯器都可以隨意編譯代碼,並且每次編譯都可以得到不同的結果。

例如,在某些項目上,C ++被編譯為Microsoft CLR(C ++ / CX)的托管擴展。 由於所有內容都已經有對堆上對象的引用,所以我想沒有區別。

對於非托管編譯,答案並不簡單。 當我想到“ XXX的運行速度會比YYY的運行速度快嗎?”時,就會想到幾個問題,例如:

  • 你反對的話是不合情理的嗎?
  • 您的編譯器是否支持返回值優化?
  • 您的對象支持僅復制語義還是同時復制和移動?
  • 該對象是以連續方式打包的(例如std::array )還是在堆上具有指向某個對象的指針? (例如std::vector )?

如果我給具體的例子,我的猜測是,在MSVC ++和GCC,返回std::vector按值將是通過引用傳遞它,因為R值優化,並且將是一個 (由幾納秒)快然后通過移動返回向量。 例如,這在Clang上可能完全不同。

最終,分析是這里唯一的真實答案。

在大多數情況下,應使用返回對象的方法,這是因為進行了稱為復制刪除的優化。

但是,根據打算使用函數的方式,最好通過引用傳遞對象。

例如,看一下std::getline ,它通過引用獲取一個std::string 該函數旨在用作循環條件,並不斷填充std::string直到達到EOF。 使用相同std::string允許的存儲空間std::string在每次循環可以重復使用,大大減少將要進行必要的內存分配的數量。

一些答案已經涉及到這一點,但是我想根據編輯來強調

就上下文而言,此問題僅限於與硬件平台無關的差異,並且在大多數情況下也限於軟件。 是否存在與機器無關的性能差異?

如果這是問題的極限,那么答案是沒有答案。 C ++規范沒有規定如何在性能上實現對象的返回或按引用傳遞,而僅規定了兩者在代碼方面的語義。

因此,編譯器可以自由地將一個代碼優化為與另一個代碼相同的代碼,前提是這不會對程序員產生明顯的影響。

有鑒於此,我認為最好使用最直觀的情況。 如果函數確實是由於某個任務或查詢的結果而“返回”一個對象,則將其返回,而如果該函數正在對外部代碼擁有的某個對象執行操作,則按引用傳遞。

您不能對此概括性能。 首先,請進行直觀的操作,並查看目標系統和編譯器對其進行優化的程度。 如果在分析后發現問題,請根據需要進行更改。

我們不能100%通用,因為不同的平台具有不同的ABI,但是我認為我們可以做出一些通用的聲明,這些聲明將適用於大多數實現,但需要注意的是,這些東西大多數都適用於未內聯的函數。

首先讓我們考慮原始類型。 在較低的級別上,使用指針來實現參數的引用傳遞,而原始返回值通常在字面上傳遞給寄存器。 因此,返回值可能會表現更好。 在某些體系結構上,同樣適用於小型結構。 復制一個足夠小的值以放入一個或兩個寄存器非常便宜。

現在讓我們考慮更大但仍然很簡單(沒有默認構造函數,副本構造函數等)的返回值。 通常,較大的返回值是通過向函數傳遞指向應放置返回值的位置的指針來處理的。 復制省略允許將函數返回的變量,用於返回的臨時變量以及放置結果的調用方中的變量合並為一個。 因此,傳遞的基礎與按引用傳遞和返回值傳遞的基本相同。

總的來說,對於原始類型,我希望返回值會稍微好一些;對於較大但仍然很簡單的類型,我希望它們相同或更好,除非您的編譯器非常擅長復制省略。

對於使用默認構造函數,復制構造函數等的類型,事情變得更加復雜。 如果多次調用該函數,則每次返回值都會強制重新構造對象,而參考參數可能會允許數據結構被重用而無需重構。 另一方面,在調用函數之前,參考參數將強制進行(可能是不必要的)構造。

此偽代碼功能:

not-void function-name () {
    do-something
    return value;
}

當返回值不需要對其進行任何進一步修改時,最好使用該方法。 傳遞的參數只能在function-name修改。 不再需要引用它。


否則相同的偽代碼函數:

void function-name (not-void& arg) {
    do-something
    arg = value;
}

如果我們有另一種方法來調節相同變量的值,例如,並且我們需要通過任一調用來保持對變量所做的更改,則將很有用。

void another-function-name (not-void& arg) {
    do-something
    arg = value;
}

從性能角度看,盡管小物件的差異可能微不足道,但通常來說,副本會更昂貴。 同樣,您的編譯器可能會將返回副本優化為移動,使其等效於傳遞引用。

除非您有充分的理由,否則我建議不要傳遞非const引用。 使用返回值(例如tryGet()排序的函數)。

正如其他人已經說過的,如果您願意,您可以衡量自己的差異。 對這兩個版本運行測試代碼數百萬次,並觀察兩者之間的區別。

暫無
暫無

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

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