簡單說明:
C++ Short String Optimization 指C++針對(duì)短字符串的優(yōu)化彤路。
- 默認(rèn)情況下剩辟,C++的std::string都是存儲(chǔ)在heap中池充,導(dǎo)致訪問std::string需要經(jīng)過一次尋址過程眠冈,速度較慢飞苇,并且這種實(shí)現(xiàn)的空間局部性不好,對(duì)cache的利用較低蜗顽。
- 很多string的字符串長度很小布卡,這個(gè)時(shí)候,我們可以把字符串存儲(chǔ)到棧上雇盖,從而不需要進(jìn)行內(nèi)存分配忿等,優(yōu)化創(chuàng)建速度,并且訪問棧上數(shù)據(jù)的局部性很好崔挖,速度比較快贸街。
std::string需要存儲(chǔ)如下變量:字符串所在地址,字符串大小size狸相,字符串已經(jīng)申請(qǐng)的內(nèi)存capacity薛匪,加上短字符串
類似如下struct聲明:
struct String {
char* addr;
size_t size;
size_t capacity;
char[16] short_string;
}
在現(xiàn)在的C++編譯器中,因?yàn)楫?dāng)小于一定大小時(shí)脓鹃,我們沒有使用堆逸尖,所以不需要存儲(chǔ)addr指針,并且此時(shí)的容量就是短字符串容量瘸右,也不需要存儲(chǔ)娇跟,所以短字符串緩存可以和這兩者復(fù)用,樣例如下:
struct String {
size_t size;
union {
struct {
size_t capacity;
char* addr;
}
char[16] short_string;
}
}
這時(shí)太颤,string沒有使用額外的空間苞俘,并且能夠針對(duì)端字符串進(jìn)行優(yōu)化,是一個(gè)很好的優(yōu)化方式龄章。
我自己的測試代碼:
#include <iostream>
#include <string>
#include <vector>
#include <queue>
#include <mutex>
#include <array>
using namespace std;
int main(int argc, char const* argv[])
{
int i;
vector<char> test_vector;
mutex g_mutex;
array<int, 10> test_array;
deque<char> test_deque;
string short4("test");
string long19("test test test test");
string long29("test test test test test test");
cout << "stack address: " << &i << endl;
cout << "vector sizeof: " << sizeof(test_vector) << " vector address: " << &test_vector << endl;
cout << "g_mutex sizeof: " << sizeof(g_mutex) << " g_mutex address: " << &g_mutex << endl;
cout << "test_deque sizeof: " << sizeof(test_deque) << " test_deque address: " << &test_deque << endl;
cout << "test_array sizeof: " << sizeof(test_array) << " test_array address: " << &test_array << endl;
cout << "sizeof short4: " << sizeof(short4) << " address: " << &short4 << " char address: " << (void*)(&(short4[0])) << endl;
cout << "sizeof long19: " << sizeof(long19) << " address: " << &long19 << " char address: " << (void*)(&(long19[0])) << endl;
cout << "sizeof long29: " << sizeof(long29) << " address: " << &long29 << " char address: " << (void*)(&(long29[0])) << endl;
return 0;
}
輸出:
stack address: 0x7fff5d7eeca4
vector sizeof: 24 vector address: 0x7fff5d7eec88
g_mutex sizeof: 64 g_mutex address: 0x7fff5d7ef210
test_deque sizeof: 48 test_deque address: 0x7fff5d7eec30
test_array sizeof: 40 test_array address: 0x7fff5d7eec60
sizeof short4: 24 address: 0x7fff5d7eec08 char address: 0x7fff5d7eec09 —— 在棧上分配
sizeof long19: 24 address: 0x7fff5d7eebf0 char address: 0x7fff5d7eebf1 —— 棧上
sizeof long29: 24 address: 0x7fff5d7eebd8 char address: 0x7fdf27402710 —— 堆上
工具提示:
在線在 Visual C++里執(zhí)行C++代碼: http://webcompiler.cloudapp.net/
在線在gcc吃谣,clang里面執(zhí)行C++代碼: https://wandbox.org/
參考:
回答翻譯:
在automatic variables(『棧上』瓦堵,指沒有通過malloc/new常見的變量)上的操作通常要比在free store(『堆上』基协,通過malloc/new創(chuàng)建的變量)。但是菇用,棧的大小是在編譯期確定的澜驮,而堆的大小不是的。并且惋鸥,棧有大小限制(一般是幾M)杂穷,而堆的大小只受限于你的系統(tǒng)內(nèi)存大小悍缠。
SSO是 Short/Small String Optimization. 一個(gè)std::string通常是存儲(chǔ)指向堆空間的指針,這和調(diào)用new char[size]的性能類似耐量。這樣做能夠防止在非常大的string時(shí)不會(huì)出現(xiàn)棧溢出飞蚓,不過它會(huì)比較慢,尤其是在復(fù)制操作時(shí)廊蜒。作為一個(gè)優(yōu)化趴拧,需要std::string的實(shí)現(xiàn)創(chuàng)建一個(gè)小的棧上的數(shù)組,例如:char[20]. 如果你的string長度小于等于20(這是一個(gè)例子山叮,與實(shí)際大小不同), 它直接把數(shù)據(jù)保存到這個(gè)數(shù)組中著榴。這去除了new調(diào)用,從而提高了一些速度屁倔。
EDIT:
我沒有想到這個(gè)回答現(xiàn)在這么火脑又,但是因?yàn)樗_實(shí)很火,我在下面給出一個(gè)更加實(shí)際的實(shí)現(xiàn)锐借,但是請(qǐng)注意我沒有度過真實(shí)世界的SSO實(shí)現(xiàn)问麸。
實(shí)現(xiàn)詳情:
std::string最少需要下述信息:
- 大小 size
- 容量 capacity
- 數(shù)據(jù)的位置 the location of data
大小可以被存儲(chǔ)為std::string::size_type 或者指向末尾的指針。唯一的區(qū)別是是在用戶調(diào)用size時(shí)兩個(gè)指針相減钞翔,還是在用戶調(diào)用end時(shí)對(duì)開始指針加一個(gè)數(shù)严卖。capacity也可以以這兩種方式存儲(chǔ)。
你不需要為你不會(huì)使用的東西付款
首先嗅战,考慮我在上面提到的最原始的實(shí)現(xiàn)方式:
class string {
public:
// all 83 member functions
private:
std::unique_ptr<char[]> m_data;
size_type m_size;
size_type m_capacity;
std::array<char, 16> m_sso;
};
在64位系統(tǒng)上妄田,這通常意味著std::string擁有24個(gè)字節(jié)的額外開支俺亮,加上16個(gè)字節(jié)的SSO緩存(選擇16而不是20的原因是因?yàn)閷?duì)其要求)驮捍。其實(shí)沒有必要像原始實(shí)現(xiàn)里那樣同時(shí)存儲(chǔ)三個(gè)數(shù)據(jù)成員和一個(gè)字符串?dāng)?shù)組。如果m_size <= 16脚曾,我會(huì)字符串放到m_sso东且,我知道了capacity,并且我知道我不需要指向數(shù)據(jù)的指針本讥。如果m_size > 16, 我就不需要m_sso珊泳。沒有任何情況我同時(shí)需要他們。一個(gè)更聰明的做法可能是如下這樣(沒有經(jīng)過測試拷沸,只能作為樣例用):
class string {
public:
// all 83 member functions
private:
size_type m_size;
union {
class {
// This is probably better designed as an array-like class
std::unique_ptr<char[]> m_data;
size_type m_capacity;
} m_large;
std::array<char, sizeof(m_large)> m_small;
};
};
我認(rèn)為大多數(shù)的實(shí)現(xiàn)和這個(gè)比較類似色查。
[Meaning of acronym SSO in the context of std::string 各個(gè)編譯器實(shí)現(xiàn)使用的內(nèi)存使用情況對(duì)比,包含在stack heap中分配的內(nèi)存撞芍,以及可用的capacity Why does libc++'s implementation of std::string take up 3x memory as libstdc++?] (https://stackoverflow.com/questions/27631065/why-does-libcs-implementation-of-stdstring-take-up-3x-memory-as-libstdc/28003328#28003328)
回答翻譯:
這輛車有一個(gè)簡短的程序來幫助你探索std::string使用的不同類型的內(nèi)存秧了,包括stack和heap
#include <string>
#include <new>
#include <cstdio>
#include <cstdlib>
std::size_t allocated = 0;
void* operator new (size_t sz)
{
void* p = std::malloc(sz);
allocated += sz;
return p;
}
void operator delete(void* p) noexcept
{
return std::free(p);
}
int
main()
{
allocated = 0;
std::string s("hi");
std::printf("stack space = %zu, heap space = %zu, capacity = %zu\n",
sizeof(s), allocated, s.capacity());
}
使用 http://melpon.org/wandbox/ 可以很容易的得到不同編譯器、庫組合的結(jié)果序无,例如:
gcc 4.9.1
stack space = 8, heap space = 27, capacity = 2
gcc 5.0.0
stack space = 32, heap space = 0, capacity = 15
clang/libc++
stack space = 24, heap space = 0, capacity = 22
VS-2015: from( http://webcompiler.cloudapp.net)
stack space = 32, heap space = 0, capacity = 15
上述輸出同樣帶有capacity验毡,這表示度量string能夠在堆中新分配一個(gè)更大的緩存前能夠容納多少個(gè)chars衡创。在gcc5.0.0, libc++和VS2015的實(shí)現(xiàn)中,這表示的是短字符串緩存的大小晶通。即:在棧上分配璃氢,用來存儲(chǔ)短字符串緩存的大小,這時(shí)可以避免更代價(jià)高昂的堆內(nèi)存分配狮辽。
這里發(fā)現(xiàn)libc++中的短字符串實(shí)現(xiàn)使用了最小的stack usage一也,并且包含了最大的短字符串緩存。并且如果你統(tǒng)計(jì)總共內(nèi)存使用(stack + heap)喉脖,在這四種2-character的string實(shí)現(xiàn)中塘秦,libC++使用了最小的總內(nèi)存。
需要注意的是动看,這些測試都是在64位機(jī)器上運(yùn)行的尊剔。在32位系統(tǒng)中,libc++的stack大小降到12菱皆,并且短字符串緩存大小降到10须误。我不知道其他實(shí)現(xiàn)在32位的表現(xiàn),不過你可以使用上述代碼來找到相應(yīng)結(jié)果仇轻。
[What are the mechanics of short string optimization in libc++? Libc++ 實(shí)現(xiàn)] (https://stackoverflow.com/questions/21694302/what-are-the-mechanics-of-short-string-optimization-in-libc)
回答翻譯:
libc++的basic_string在所有架構(gòu)上被設(shè)計(jì)為sizeof大小為3個(gè)words京痢,在這里sizeof(word) == sizeof(void*)。你已經(jīng)正確分析出long/short標(biāo)志篷店,以及在short string類型時(shí)的size區(qū)域祭椰。
what value would __min_cap, the capacity of short strings, take for different architectures?
那么__min_cap,short string的容量疲陕,在各個(gè)架構(gòu)上怎么計(jì)算的方淤?
在short string的情況下,下面是string實(shí)現(xiàn)的3個(gè)words所做的工作
- 1一個(gè)bit用來表示short/long標(biāo)志
- 7個(gè)bits表示字符串當(dāng)前大小
- 在char的情況下蹄殃,一個(gè)字節(jié)用來表示字符串末尾的null(libc++會(huì)在數(shù)據(jù)后面一致保存一個(gè)結(jié)束的null)
這種情況下携茂,剩下3 * words - 2 bytes的數(shù)據(jù)來存儲(chǔ)短字符串(也就是短字符串不進(jìn)行內(nèi)存分配時(shí)最大的capacity())
在32位模式下,短字符串可以放置10個(gè)chars诅岩,sizeof(string) = 12 (3 * 4).
在64位模式下讳苦,短字符串可以放置22個(gè)chars,sizeof(string) = 24 (3 * 8).
這里的一個(gè)主要設(shè)計(jì)目標(biāo)是:在最小化sizeof(string)的同時(shí)吩谦,讓內(nèi)部的buffer盡可能大鸳谜。其中的根本原因是加速move語義構(gòu)造和move語義賦值。當(dāng)sizeof越大時(shí)式廷,就需要在move語義構(gòu)造和move語義賦值時(shí)移動(dòng)更多的字節(jié)咐扭。
在長字符串的情況,至少需要三個(gè)words來分別存儲(chǔ) 數(shù)據(jù)指針(pointer)、大小(size)草描、容量(capacity)踩麦,因此我(作者)把短字符串格式限制到這三個(gè)words中昵观。曾經(jīng)有人提出4個(gè)words大小的sizeof可能會(huì)有更好的性能。但是我(作者)沒有測試過這種設(shè)計(jì)選擇。
[更精致的實(shí)現(xiàn) SSO23绰播,還分析了相關(guān)各個(gè)實(shí)現(xiàn)的細(xì)節(jié)] (https://github.com/elliotgoodrich/SSO-23)