礦坑系列 ── Small String Optimization (SSO)
礦坑系列 ── Small String Optimization (SSO)
礦坑系列首頁:首頁
hackmd 版首頁:首頁
Small String Optimization (SSO)
source:Small String Optimization in C++
首先要注意 SSO 是 optional 的,也就是不被保證,但在大多數的編譯器都有實作。
通常我們不太喜歡 string 的原因是其常常會試圖去 allocate 記憶體空間,而 heap allocation 又可能會造成效能上的影響,因此 C++ 內的 std::string
有一項針對長度較短的字串去做優化,就是標題所寫的 SSO。
核心概念就是我們可以先分配一小塊 stack 段的記憶體,如果字串夠短就可以直接塞到這個 stack 內,如果字串太長,塞不下,就拿這個空間放 pointer,指向 heap 段存取的字串。
而字串長度具體是多少以下才可以去做優化這就要看你用的 lib 了,在 VS2019 的 msvc 裡是 15 個字,而在我的環境上(mingw-gcc 11.2.0) 也是 15。
很多人可能會覺得 std::string
會有 heap allocation 因此不去用他:
1 |
|
輸出:
1 | str: |
上面我們 overload 了 new
operator,因此當它要 allocate heap 空間時就會多輸出一段文字,而我們可以看見 str
因為只有 15 個字,所以不會分配 heap 段的記憶體,但 str2
就有了,這裡分配的是 17 bytes,clang 14.0.0 也是分配 17 bytes,然而 VS2019 的 msvc 則是 32 bytes。
如果你的環境上沒有跑出一樣的結果大概是因為你那邊它有自己的 extension 我猜,如 tcc、icc file 之類的,內部實作可能就有些許差異,又或者你是在 Debug 模式下輸出的結果也有可能不同。
挖礦,入坑看魔法
在一開始先說一下,下面的 code 只是其中一種實作方式,不代表每個 lib 都是這樣做的,後面就以我的環境為例帶大家看一下 code。
如果我們進到 std::string
的內部去看會發現 std::string
是一種元素為 char
的 basic_string
:
1 | template <class _Elem, class _Traits = char_traits<_Elem>, class _Alloc = allocator<_Elem>> |
而當我們去找這裡對應的建構子,會看見這個:
1 | _CONSTEXPR20 basic_string(_In_reads_(_Count) const _Elem* const _Ptr, _CRT_GUARDOVERFLOW const size_type _Count) |
以 std::stirng
來說,就是吃一個 const char pointer,這裡的重點是 assign(_Ptr, _Count);
函式,_Ptr
指的是實際的 pointer,只像我們的 "Name"
,而 _Count
則是有幾個字,以這裡來說會是 4。
我們再繼續往這個 assign
函式看,會看見它長這樣:
1 | _CONSTEXPR20 basic_string& assign( |
你會看見裡面有一個 if
判斷式,如果 _Count
小於某個值,那就會拿到 stack 段 buffer 的指標 _Old_ptr
,並直接把我們的字串移動到 buffer 內,然後 return,因此完全沒有多餘的 allocation。
但如果它沒有進到上面那個 if-statement,也就是 _Count
比某個值還大,那就會去用到下面那個 _Reallocate_for
函式,這個 function 內有一行是這個:
1 | const pointer _New_ptr = _Al.allocate(_New_capacity + 1); // throws |
也就是說去動到了 allocation。
而這個關鍵的值 _Myres
在一個叫做 _String_val
的 class 裡面,這個 class 的最下面有著這兩行:
1 | size_type _Mysize = 0; // current length of string |
而關於 _Myres
的賦值在這裡:
1 | void _Become_small() { |
在最下面你可以看見它被設定為 _BUF_SIZE-1
的大小,而 _BUF_SIZE
的大小則定義在 basic_stirng
裡面:
1 | static constexpr auto _BUF_SIZE = _Scary_val::_BUF_SIZE; |
而 _Scary_val::_BUF_SIZE
則定義在 _String_val
裡面:
1 | // length of internal buffer, [1, 16]: |
以我們的例子來說會是 16,而 _Myres
的值還需要再 -1,也就是 15,因此當超過 15 個字的時候就會呼叫 heap allocation。