就在近日,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.h 和 folly/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

漫步Facebook开源C++库Folly之string类设计(散列、字符串、向量、内存分配、位处理等,小部分是对现有标准库和Boost库功能上的补充,大部分都是基于性能的需求而“重新制造轮子”)的更多相关文章

  1. c++ string类使用及用string类解决整行字符串输入

    下面随笔给出c++ string类使用及用string类解决整行字符串输入. string类 使用字符串类string表示字符串 string实际上是对字符数组操作的封装 string类常用的构造函数 ...

  2. 从字符串到常量池,一文看懂String类设计

    从一道面试题开始 看到这个标题,你肯定以为我又要讲这道面试题了 // 这行代码创建了几个对象? String s3 = new String("1"); 是的,没错,我确实要从这里 ...

  3. 基于标准库的string类实现简单的字符串替换

    感觉基本功还是不扎实,虽然能做些程序但是现在看来我还是个初学者(primer),试着完成习题结果还得修修改改. 废话不多说,实现功能很简单,<C++ Primer>9.5.2节习题. // ...

  4. String类练习统计一个字符串中大小写字母及数字字符个数

    public class StringPractice { public static void main(String[] args) { //创建一个文本扫描器 Scanner sc = new ...

  5. 109、Java中String类之截取部分子字符串

    01.代码如下: package TIANPAN; /** * 此处为文档注释 * * @author 田攀 微信382477247 */ public class TestDemo { public ...

  6. String类自带的字符串处理原生方法

    一.取出指定索引的字符 —— 使用charAt()方法 二.字符串与字符数组的转换 三.字符串转大写.先转换成数组,然后再改变ASCII码 四.给定一个字符串,要求判断其是否由数字组成 五.字符串与字 ...

  7. C++标准模板库Stand Template Library(STL)简介与STL string类

    参考<21天学通C++>第15和16章节,在对宏和模板学习之后,开启对C++实现的标准模板类STL进行简介,同时介绍简单的string类.虽然前面对于vector.deque.list等进 ...

  8. Java进阶01 String类

    链接地址:http://www.cnblogs.com/vamei/archive/2013/04/08/3000914.html 作者:Vamei 出处:http://www.cnblogs.com ...

  9. (转)C++——std::string类的引用计数

    1.概念 Scott Meyers在<More Effective C++>中举了个例子,不知你是否还记得?在你还在上学的时候,你的父母要你不要看电视,而去复习功课,于是你把自己关在房间里 ...

随机推荐

  1. Eclipse使用技巧总结(六)

    四十.增量查找 Ctrl + J : Ctrl + Shift + J: 四十一.快速跳到某行 Ctrl + L 四十二.快速比较不同 Window-->Preference___查找quick ...

  2. vmware合并多个虚拟硬盘文件(使用vmware-vdiskmanager.exe)

    有时,当我们创建虚拟机vmware里面的应用程序,我们可能会选择创建分割的虚拟磁盘中的多个文件2GB的文件,这是为了提高复制过程,主要用于存储虚拟机文件系统不支持创建更大的文件.还有种情况是虚拟化物理 ...

  3. HTML:描述语义

    一.HTML HTML:Hypertext Markup Launguage,超文本标记语言,是网页的就文件格式,用于描述网页语义. 二.HTML骨架 DTD手册:http://www.w3schoo ...

  4. wpf中xaml的类型转换器与标记扩展

    原文:wpf中xaml的类型转换器与标记扩展 这篇来讲wpf控件属性的类型转换器 类型转换器 类型转换器在asp.net控件中已经有使用过了,由于wpf的界面是可以由xaml组成的,所以标签的便利也需 ...

  5. QList介绍(QList比QVector更快,这是由它们在内存中的存储方式决定的。QStringList是在QList的基础上针对字符串提供额外的函数。at()操作比操作符[]更快,因为它不需要深度复制)非常实用

    FROM:http://apps.hi.baidu.com/share/detail/33517814 今天做项目时,需要用到QList来存储一组点.为此,我对QList类的说明进行了如下翻译. QL ...

  6. 阐述php(五岁以下儿童) 注意事项和使用功能

    1.函数声明 <?php /** * function 函数名(參数1, 參数2.... ){ * 函数体; * 返回值; * } */ $sum = sum(3, 4); echo $sum; ...

  7. 新建py文件时取名千万要小心 不要和已有模块重名

    这是因为我新建了一个email.py的文件 后来我将文件名rename成了myemail.py没有看改名提示,结果导致所有的对email的import和调用全部改成了对myemail的import和调 ...

  8. 《得知opencv》注意事项——矩阵和图像处理——cvAdd、cvAddS and cvAddWeighted

    矩阵和图像操作 (1)cvAdd函数 其结构 void cvAdd(//图像加和 const CvArr* src1,//第一个原矩阵 const CvArr* src2,//第二个原矩阵 CvArr ...

  9. Golang的演化历程

    本文来自Google的Golang语言设计者之一Rob Pike大神在GopherCon2014大会上的开幕主题演讲资料“Hello, Gophers!”.Rob大神在这次分 享中用了两个生动的例子讲 ...

  10. wpf采用Xps实现文档显示、套打功能

    原文:wpf采用Xps实现文档显示.套打功能 近期的一个项目需对数据进行套打,用户要求现场不允许安装office.页面预览显示必须要与文档完全一致,xps文档来对数据进行处理.Wpf的Document ...