考慮R值引用,c++下多字元串連接,如何寫更高效?
比如有以下的兩個std::string,
std::string s1 = "s1";std::string s2 = "s2"// 寫法1s1 += s2 + " is " + s1 + ", string";
// 寫法2s1.append(s2).append("is").append(s1).append(",string");或者有更好的寫法?請給出合理解釋。謝謝
若你這樣寫的話,其實瓶頸不會是在你認為的地方,如是否有右值引用等。若你去profile,你會發現你的這兩種寫法的瓶頸在 += / append 上。而這個回答,我也想要凸顯profile的重要性,所以會有很多的圖,以及詳細的步驟。
我們舉一個簡單的例子:
#include &
#include &
#include &
#define VER 1
int main() 20次就可以反映問題了。 我們首先定義 VER 為 1,然後去用Profile. 由於Mac下我沒有找到gperf,所以我使用了XCode的Instruments來進行工作。 首先,我們來跑一下O3的情況下,其時間為多少:
{
typedef std::chrono::high_resolution_clock clock;
typedef std::chrono::duration&
std::string str;
std::string s1 = "s1";
std::string s2 = "s2";
auto t0 = clock::now();
#if VER == 1
for (int i = 0; i &< 20; ++i)
{
s1 += s2 + "is" + s1 + ",string";
}
#elif VER == 2
for (int i = 0; i &< 20; ++i)
{
s1.append(s2).append("is").append(s1).append(",string");
}
#endif
auto t1 = clock::now();
std::cout &<&< mil(t1 - t0).count() &<&< "ms
";
}
然後,我們直接跑一下第二種的情況:
乍看之下,似乎第二種比第一種好很多,所以是不是跟右值引用有很大的關係呢?其實這都只是表象,若我們還想更大的提高,需要更深次的看。若我們去profile一下,分別觀測一下這兩種情況:
在XCode中,我們可以選用Instruments來達到我們的目的,如下所示:
VER 1:
然後我們點擊出現的函數下拉小三角:
於是,我們在VER 1寫下這樣的一行代碼:
然後,我們編譯測試:
我們再次profile:
我們設定好更好的參數:
然後再次運行:差不多11 ~ 12左右吧。然後我們再次profile:如我們所願,沒有了__grow_by_and_replace。那麼,我們可以發現VER 1的寫法是不是變成了VER 2的寫法了?然後只是多了一個reserve,於是我們把reserve加到VER 2中:我們會發現平均也是11 ~ 12ms,和 VER 1 持平了(畢竟一個寫法)。然後profile也是如此:沒有了__grow_by_and_replace.於是,我們完成了從平均38 ~ 39 降到 11 ~ 12 的工作,寫法的改進都是自然而然的,一切的根據都是: Profile and Data.c++的字元串操作,想提高效率,就兩點1)根據需要調整capacity;2)避免生成新的string obj
當然是用rope啦http://llvm.org/docs/doxygen/html/classllvm_1_1Twine.html
《imprefect C++》里有個高效拼接字元串的例子,基本就是是寫法2優化再加語法糖。
語法糖比較簡單,2B青年可以用逗號,正常青年用&<&<就行。
優化無非是少分配,少拷貝,能分配在棧上(如果有格式化數字之類需求要注意這一條),就別放堆上。
就題主的例子,無非在2的基礎上,先統計所有子串的長度,最後只分配一次,集中拷貝。大致做法就是先生成一個棧上臨時對象列表,表達式最後統一處理。
沒有語法糖的形式大概這樣:joinstr(s1,strref(s2)+strref(s3)+strref(s4)).當然分配一次這種優化,除非表達式特別長也沒啥顯著效果
推薦閱讀:
※C#轉C++開發,該歷經怎樣的學習路線?
※如何評價Qt Lite Project?
※什麼時候用異常,什麼時候用斷言?
※以後想做大型遊戲(至少是端游,不是手游),不知道是不是一定需要精通C++或者熟練?
※為什麼老師不推薦用《C++ Primer》作為教材?
TAG:C |