C++ Short String Optimization stackoverflow回答集錦以及我的思考

簡單說明:
C++ Short String Optimization 指C++針對(duì)短字符串的優(yōu)化彤路。

  1. 默認(rèn)情況下剩辟,C++的std::string都是存儲(chǔ)在heap中池充,導(dǎo)致訪問std::string需要經(jīng)過一次尋址過程眠冈,速度較慢飞苇,并且這種實(shí)現(xiàn)的空間局部性不好,對(duì)cache的利用較低蜗顽。
  2. 很多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/

參考:

[什么是SSO?] (https://stackoverflow.com/questions/10315041/meaning-of-acronym-sso-in-the-context-of-stdstring/10319672#10319672)

回答翻譯:

在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)

寫作方式參考基茵,從string可能會(huì)占據(jù)多少字符串開始講起

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末墙歪,一起剝皮案震驚了整個(gè)濱河市频伤,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌术浪,老刑警劉巖瓢对,帶你破解...
    沈念sama閱讀 216,372評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異胰苏,居然都是意外死亡硕蛹,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門硕并,熙熙樓的掌柜王于貴愁眉苦臉地迎上來法焰,“玉大人,你說我怎么就攤上這事倔毙“R牵” “怎么了?”我有些...
    開封第一講書人閱讀 162,415評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵陕赃,是天一觀的道長卵蛉。 經(jīng)常有香客問我,道長么库,這世上最難降的妖魔是什么傻丝? 我笑而不...
    開封第一講書人閱讀 58,157評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮廊散,結(jié)果婚禮上桑滩,老公的妹妹穿的比我還像新娘梧疲。我一直安慰自己允睹,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評(píng)論 6 388
  • 文/花漫 我一把揭開白布幌氮。 她就那樣靜靜地躺著缭受,像睡著了一般。 火紅的嫁衣襯著肌膚如雪该互。 梳的紋絲不亂的頭發(fā)上米者,一...
    開封第一講書人閱讀 51,125評(píng)論 1 297
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼蔓搞。 笑死胰丁,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的喂分。 我是一名探鬼主播锦庸,決...
    沈念sama閱讀 40,028評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼蒲祈!你這毒婦竟也來了甘萧?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,887評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤梆掸,失蹤者是張志新(化名)和其女友劉穎扬卷,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體酸钦,經(jīng)...
    沈念sama閱讀 45,310評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡怪得,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評(píng)論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了卑硫。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片汇恤。...
    茶點(diǎn)故事閱讀 39,690評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖拔恰,靈堂內(nèi)的尸體忽然破棺而出因谎,到底是詐尸還是另有隱情,我是刑警寧澤颜懊,帶...
    沈念sama閱讀 35,411評(píng)論 5 343
  • 正文 年R本政府宣布财岔,位于F島的核電站,受9級(jí)特大地震影響河爹,放射性物質(zhì)發(fā)生泄漏匠璧。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評(píng)論 3 325
  • 文/蒙蒙 一咸这、第九天 我趴在偏房一處隱蔽的房頂上張望夷恍。 院中可真熱鬧,春花似錦媳维、人聲如沸酿雪。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽指黎。三九已至,卻和暖如春州丹,著一層夾襖步出監(jiān)牢的瞬間醋安,已是汗流浹背杂彭。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評(píng)論 1 268
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留吓揪,地道東北人亲怠。 一個(gè)月前我還...
    沈念sama閱讀 47,693評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像柠辞,于是被迫代替她去往敵國和親赁炎。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容

  • 【轉(zhuǎn)載】原文地址:std::string詳解作者:kieven2008 之所以拋棄char*的字符串而選用C++標(biāo)...
    VAYY閱讀 642評(píng)論 0 2
  • c++的字符串類std::string能否存儲(chǔ)二進(jìn)制字符以及字符'\0'钾腺? 要解決這個(gè)問題徙垫,我們首先要了解c++的...
    CodingCode閱讀 7,201評(píng)論 0 5
  • 一放棒、字符串操作 strcpy(p, p1) 復(fù)制字符串 strncpy(p, p1, n) 復(fù)制指定長度字符串 s...
    JaiUnChat閱讀 1,657評(píng)論 0 7
  • 前言 把《C++ Primer》[https://book.douban.com/subject/25708312...
    尤汐Yogy閱讀 9,516評(píng)論 1 51
  • 花瓣曬了一下午太陽间螟, 落在地上, 撿起它們厢破, 去聽很多很多的音樂, 記得笆焰, 某時(shí)某刻见坑, 某地 有一個(gè)姑娘 采集回來...
    關(guān)馨仁閱讀 141評(píng)論 0 2