漫步Facebook开源C++库folly(1):string类的设计

就在近日,Facebook宣布开源了内部使用的C++底层库,总称folly,包括散列、字符串、向量、内存分配、位处理等,以满足大规模高性能的需求。

这里是folly的github地址:https://github.com/facebook/folly

在folly项目的Overview.md中,谈到了folly库的初衷:

It complements (as opposed to competing against) offerings such as Boost and of course std. In fact, we embark on defining our own component only when something we need is either not available, or does not meet the needed performance profile.

除了小部分是对现有标准库和Boost库功能上的补充,大部分都是基于性能的需求而“重新制造轮子”。

特别是大规模下的性能需求,大规模下的性能追求是Folly统一的主题:

Good performance at large scale is a unifying theme in all of Folly.

 

 

为什么先谈string类?

一是因为string几乎是C++程序中最常用的“容器”,性能至关重要;

二是因为之前也曾写过一篇博客《std::string的Copy-on-Write:不如想象中美好》,研究了std::string的copy-on-write实现的优缺点,因此想要看看Facebook究竟需要什么样的string。

 

folly自定义的string(以下简称为fbstring)的核心实现位于 folly/FBString.h

还有一些fbstring的辅助函数(如向std::string的转换、各种格式的输出、escape、demangle等),位于 folly/String.hfolly/String.cpp ,由于本文主要谈的是fbstring的内部实现,这些内容暂且不提,有兴趣的童鞋可以自己参考源码,folly的代码还是写得相当漂亮的:)

 

folly对string类的设计和优化,主要体现在两个方面:

1. 内存模型

2. 常用方法的优化

下面将逐一说明。

 

一. 内存模型

1. 内存布局及策略

fbstring使用了三层的存储策略(three-tiered storage strategy),根据长度将fbstring分为三类:small/medium/large,分别采取不同的优化措施,以达到最佳性能。

                              fbstring内存模型示意图(使用LucidChart绘制):

 

简单来说:

短string:直接放在(栈上)对象中,避免了动态内存分配的开销。结构体长度为24字节,减去末尾的1字节(用来表示长度)和为结束符'\0'(data()和c_str()方法的需要)预留的1字节,可以放置22字节的有效长度。

中等string(小于255字节):直接通过malloc分配,并且采用eager-copy的方式,即字符串的复制总是会重新分配并拷贝内容。

至于为什么不用copy-on-write:

1. 我之前的博客也提到,copy-on-write的额外开销(原子操作、容易失效)一定程度上抵消了减少一次内存分配和拷贝带来的好处

2. folly鼓励使用jemalloc来代替glibc下默认的ptmalloc2,并且在代码中迎合jemalloc的使用做了大量优化。在这里,分配一个小片内存区域的开销是极小的,下文还会有说明。

较长string(大于255字节):使用copy-on-write,减少分配和拷贝大内存的开销。在这里,folly使用了C++11中的原子变量:std::atomic<size_t>来管理引用计数,并在引用计数减为0时销毁内存。

PS:使用capacity最高位的4个bits来判断string的种类,folly假定机器的字节序为小端(little endian),适用于x86-64平台上的大部分OS。

 

2. 内存分配器

与std::string不同,fbstring并没有从模板参数之一的Allocator获取内存,而是直接使用malloc/free管理内存

 

fbstring推荐使用jemalloc而不是Linux下glibc默认的ptmalloc2来管理动态内存:

1. 作为FreeBSD上的默认分配器,jemalloc在多线程并发的环境下表现更好(与google开源的tcmalloc性能相近)。

在tcmalloc的论文《TCMalloc : Thread-Caching Malloc》中,提到了ptmalloc2在多线程环境下的一个致命缺陷:

ptmalloc2同样通过为不同的线程分配自己的内存池(Arena)的方式来减少并发分配时的锁冲突,但ptmalloc2中线程拥有的内存池是不能迁移的,在某些情况下能够带来巨大的内存浪费:比如一个线程在开始阶段分配了300MB的内存进行初始化工作,然后释放了,但接下来的线程分配到不同的内存池,那么之前的300MB是无法重复利用的。

 

2. folly如果检测到使用jemalloc,那么将使用jemalloc的一些非标准扩展接口来提高性能。

PS:folly通过定义弱符号(weak symbol)的方法来运行时判断是否使用了jemalloc:

extern "C" int rallocm(void**, size_t*, size_t, size_t, int) __attribute__((weak));

/**
 * Determine if we are using jemalloc or not.
 */
inline bool usingJEMalloc() {
  return rallocm != NULL;
}

如果使用了jemalloc,一个典型的优化是使用jemalloc特有的rallocm来代替标准的realloc方法。(下面还会提到realloc的优化)

同时,所有动态内存请求的大小都会经过一个过滤函数:goodMallocSize(在folly/Malloc.h中)处理,以获取一个对jemalloc友好的值

goodMallocSize在不同的请求区间,将请求大小设置为64b / 256b / 4KB / 4MB对齐,以提高分配/回收效率,减少内存碎片。

 

二. 常见操作的优化

fbstring在实现时做了很多优化(如word-wise copy等),其中的细节不再一一敷述,感兴趣的读者建议去参考源码,这里只列出重要的几点:

1. 末尾'\0'的处理

fbstring的默认行为是“懒惰”添加'\0'(lazy append),即平时预留空间,只在调用data()或者c_str()时,才在结尾添加'\0',避免了每次修改字符串时的额外开销(特别是push_back操作),因为这样做是符合C++标准的。

(当然,fbstring也有相应的宏来关闭该行为)

 

2. realloc的处理

string很多时候需要realloc,为了优化realloc的效率,fbstring做了这样的设定:

(1)如果使用jemalloc:使用jemalloc的非标准接口——rallocm

(2)没有使用jemalloc:

当前内存的使用率小于50%(size * 2 < capacity),放弃使用realloc(因为realloc可能需要拷贝全部内存,而其中超过一半是无效内容),而是简单采用free+malloc+copy的方式来重新分配内存,减少拷贝开销。

当前内存的使用率大于50%,则使用realloc,寄希望realloc可以合并后面的内存(coalescing)以避免拷贝。

 

3. 优化string::find()

glibc的string::find()实现中只实现了简单的逐字符查找比较功能,复杂度为O(M*N)。(C++标准并没有规定string::find的复杂度要求)

find使用了简化的Boyer-Moore算法,代码中声称:

Casual tests indicate a 30x speed improvement over string::find()for successful searches and a 1.5x speed improvement for failed searches.

如果是简单的短字符查询,string::find()应该足够高效。只有在长字符搜索的情况下,find的BM算法实现才能体现出优势,或许这也是Facebook的常用场景吧。

 

结语:

顺便提一下,fbstring(FBString.h)的作者为Andrei Alexandrescu(熟悉C++应该都听说过),近距离欣赏大师的代码实在是一种享受。

同时,Alexandre大叔以43岁的“高龄”,依然在Facebook写着如此底层的程序。个中滋味,值得天朝所有浮躁的程序员(包括笔者在内)和“35岁论“者细细体味。

 

PromisE_谢 (Xielingjun6522@163.com) 原创文章

转载请注明出处:http://www.cnblogs.com/promise6522/archive/2012/06/05/2535530.html

posted @ 2012-06-05 01:55  PromisE_谢  阅读(13586)  评论(8编辑  收藏  举报