std::string_view 函數傳值與編譯器最佳化
std::string_view 函數傳值與編譯器最佳化
在使用 c++17
的 std::string_view
時肯定會有這個疑問:
我們該用
const string_view&
還是string_view
?
因為他本身就是 reference 的意思了,所以在函數傳值時,直接傳值比較符合語意。
可是考慮到效能:他又至少是一個 struct 包含一個指標和整數,是不是該傳 const&
?但這就像傳一個 reference 的 reference?
這裡從編譯器最佳化給出一個看法:傳值即可。
主要是用 gcc x86-64 -std=c++17 -O3
的結果。
MSVC 可以考慮使用 /O2
,也會有類似的結果。
實驗
這個實驗抽自Three reasons to pass std::string_view by value,然後再加一個 struct 的測試。
我們可以做個實驗 (godbolt):
#include <string_view>
void byvalue(std::string_view sv);
void byref(const std::string_view& sv);
void callbyvalue() {
byvalue("hello");
}
void callbyref() {
byref("hello");
}
.LC0:
.string "hello"
callbyvalue():
mov edi, 5
mov esi, OFFSET FLAT:.LC0
jmp byvalue(std::basic_string_view<char, std::char_traits<char> >)
callbyref():
sub rsp, 24
mov rdi, rsp
mov QWORD PTR [rsp], 5
mov QWORD PTR [rsp+8], OFFSET FLAT:.LC0
call byref(std::basic_string_view<char, std::char_traits<char> > const&)
add rsp, 24
ret
兩個差異在 call by value 實際上並沒有真的建立一個 std::string_view
instance。
他就只是像直接傳兩個參數到函數裡面: const char pointer 和 int 。
但是 call by reference 卻會要先建立一個東西再傳過去。
但上面的例子我們會有疑慮:這是不是只是因為傳 const char pointer 才會這樣?,
我們可以再用一個 struct 來實驗 (godbolt):
struct Foo {
int a;
const char* p;
};
void bar(int, const char*);
void byvalue(Foo sv);
void byref(const Foo& sv);
void cbar() {
bar(5, "hello");
}
void cbval() {
byvalue({5, "hello"});
}
void cbref() {
byref({5, "hello"});
}
結果
.LC0:
.string "hello"
cbar():
mov esi, OFFSET FLAT:.LC0
mov edi, 5
jmp bar(int, char const*)
cbval():
mov edi, 5
mov esi, OFFSET FLAT:.LC0
jmp byvalue(Foo)
cbref():
sub rsp, 24
mov rdi, rsp
mov DWORD PTR [rsp], 5
mov QWORD PTR [rsp+8], OFFSET FLAT:.LC0
call byref(Foo const&)
add rsp, 24
ret
可以發現,cbar
和 cbval
基本上是一樣的。他們的意思都是傳兩個參數。 cbref
就是建立一個 struct 然後傳進去。
在 -O3
的情況下,cbref
竟然意外的比 cbval
多操作!
結論
既然編譯器讓這兩個選擇的效能差異並沒有特別的突出(甚至有時會更好),那語意的選擇便更為重要:傳值就好。