string_view
使用 std::string 來做字串解析的好處是可以利用其各式各樣的功能,像是 substr
、find
等,缺點則是 std::string 實作設計成 value semantic,複製與賦值都隱含 heap 操作 ,即使是 C++11 有了 move semantic 得以避免複製,還是得付出解構的成本。為了解決這個問題,C++14 加入了 std::experimental::string_view
,這個類別可以基於 char const *
或是 std::string
建構,提供唯讀的參照到原本的記憶體。舉例如下
#include <experimental/string_view>
#include <assert>
using namespace std;
using namespace std::experimental;
int main() {
char const *data = "asdf";
string_view sv(data);
assert(data == sv.data()); // 記憶體位置相同
sv[0] = 'b'; // compiler error (string_view 是唯讀)
}
因為是唯讀參照,不需要任何 heap 上的操作,可以節省相當的成本。類似功能在 C++14 之前,許多函式庫就已經提供類似的功能,例如 re2::StringPiece 、boost::string_ref、llvm::stringref 等。這次在 C++17 看來沒太多爭議就納入標準函式庫成為 std::string_view
。
實例運用
使用 std::string 實作的簡易字串分割
輸入:“a,b,c,d”
輸出:
a
b
c
d
#include <string>
using namespace std;
struct tokenizer {
tokenizer(string const &s) : m_s(s), m_beg(0), m_end(0) {}
string operator()() {
for(;m_end < m_s.size();++m_end) {
if (m_s[m_end] == ',') {
auto res = m_s.substr(m_beg, m_end - m_beg); // heap access
m_beg = ++m_end;
return res; // heap access
}
}
if (m_end <= m_s.size())
return m_s.substr(m_beg, m_end); // heap access
}
bool more() const { return m_end < m_s.size(); }
private:
string const &m_s;
size_t m_beg, m_end;
};
void gendata(string &data, size_t count) {
for(size_t i=0; i < count; ++i) {
data.push_back('a'+ i%26);
if (i+1 != count)
data.push_back(',');
}
}
int main() {
string data;
gendata(data, 1<<23);
tokenizer tokr(data);
while(tokr.more()) {
cout << tokr() << endl;
}
return 0;
}
使用 string_view 實作的簡易字串分割
因為手邊的 compiler (gcc-4.9) 只有支援到 C++14,因此 string_view 還是位在 experimental 命名空間內
只需要增加標頭、修改 tokenizer 有用到 string 之處 (注解標示處):
#include <experimental/string_view>
using namespace std::experimental;
struct tokenizer {
tokenizer(string_view s) // change to string_view
: m_s(s), m_beg(0), m_end(0) {}
string_view operator()() { // string_view as return type
for(;m_end < m_s.size();++m_end) {
if (m_s[m_end] == ',') {
auto res = m_s.substr(m_beg, m_end - m_beg);
m_beg = ++m_end;
return res;
}
}
if (m_end <= m_s.size())
return m_s.substr(m_beg, m_end);
}
bool more() const { return m_end < m_s.size(); }
private:
string_view m_s; // change to string_view
size_t m_beg, m_end;
};
效能比較
效能的數據是單獨量測下面這個迴圈耗費的時間
string token; // and string_view token
while(tokr.more()) {
token = tokr();
}
資料量為 16M ,兩個程式各跑 10 次,測試平台是 cygwin、Windows7、16G RAM。
時間單位為 millisecond。
string | string_view |
---|---|
2523 | 22 |
2517 | 22 |
2518 | 22 |
2510 | 23 |
2513 | 22 |
2512 | 22 |
2507 | 22 |
2510 | 24 |
2516 | 21 |
2524 | 22 |
數據有點驚人,實際上拿 string 的版本到 Linux 上面跑大概只需要一半的時間,string_view 的版本由於 toolchain 還沒準備好,之後有機會再改到其他平台測試補上數據。
更新數據 (Ubuntu, clang-4.0)
string | string_view |
---|---|
557 | 19 |
526 | 19 |
533 | 19 |
511 | 19 |
532 | 18 |
611 | 21 |
512 | 20 |
499 | 20 |
513 | 23 |
510 | 19 |
Compilers Support Status
Vendor | Flag | Version |
---|---|---|
GCC | –std=c++1z | 7.0.0 |
GCC | –std=c++17 | 7.0.0 |
Clang | –std=c++1z | 3.9.0 |
Clang | –std=c++17 | 5.0.0 |
Written with StackEdit.
留言
張貼留言