folly::fbstring
是一个完全兼容std::string
的类,可以做到无缝替换,而且性能更高。其代码参见https://github.com/facebook/folly/blob/master/folly/FBString.h。
fbstring
的高性能在于,它用最紧凑的内存布局同时实现了SSO
(small string optimization)优化和COW
(copy on write)优化:
- 当字符串长度小于 24 时,它使用它自己的就地空间( in-situ )存储,不会另外分配单独的空间来存储字符串内容。此时被认为是小字符串。
- 当字符串长度大于 254 时,它将启用
COW
优化。当复制字符串时,在修改之前,将共享空间,减少内存占用以及复制操作。此时认为是大字符串。 - 长度位于中间的,被认为是中字符串。
它的栈上空间存储的实现尤其精妙。folly::fbstring
本身占用 24 个字节,却能支持最长 23 字节的栈上字符串,没有一个字节的浪费(因为还需要一个存储 0 的结束字节)。与之相比的是,std::string
占用 32 个字节,其栈上字符串的长度只有 16。
1、内存布局
folly::fbstring
能以更小的空间实现更长的就地字符串,主要原因是使用了union
结构如下:
union {
struct {
char* data;
size_t size;
size_t capacity;
} heap;
struct {
char data[23];
int8_t length; // align 1
} stack;
};
最重要的是,如何区分就地和堆。说穿了也很简单,无论是堆还是栈的情况,都用不到最后一个字节的最后两位(一个字节共八位):
- 堆上情况,最后两位是
size_t capacity
的最后两位,capacity
不可能大到用到这两位,否则将大于$2^{62}$,至少在目前,这是不可能的。 - 就地的情况,最后两位是
int8_t length
的最后两位,而length
在 0 到 23 之间,因此也用不到这两位。
因此可使用最后一个字节的最后两个位作为标志位。如果最后两个标志位都是 0 ,则认为是栈上的小字符串。如果第一个标志位是 1 ,表示堆上的中字符串,如果第二个标志位是 1 ,表示堆上的大字符串。
然后这里面还有一个很细微的技巧,使得栈上字符串长度可以达到 23 (而不是 22 !)。其原因在于,栈上存储的length
实际为23-size()
。这使得size()=23
时,length
刚好为 0 ,作为char data[]
的结束符!
我们还可以对照std::string
的内存结构,可以看到,sizeof(std::string)
等于 32 ,但只提供了 15 字节长度的小字符串优化:
struct basic_string {
char* heap;
size_t size;
union {
size_t capacity;
char sso_buffer[16];
};
};
【未完待续】
Q. E. D.