2016年11月11日 星期五

TokuDB存储引擎

TokuDB是Tokutek公司开发的基于ft-index(Fractal Tree Index)键值对的存储引擎。
它使用索引加快查询速度,具有高扩展性,并支持hot scheme modification,具有以下特点:
1. 插入性能快20~80倍;
2. 压缩数据减少存储空间;
3. 数据量可以扩展到几个TB;
4. 不会产生索引碎片;
5. 支持hot column addition,hot indexing,mvcc

适用场景:
1. 如果你要存储blob,不要使用TokuDB,因为它限制记录不能太大;
2. 如果你的记录数量过亿,使用TokuDB;
3. 如果你注重update的性能,不要使用TokuDB,它没有Innodb快;
4. 如果你要存储旧的记录,使用TokuDB;
5. 如果你想要缩小数据占用的存储空间,使用TokuDB;

下面看看TokuDB与InnoDB的对比情况,
注:上述总结和性能数据来源于淘宝梁智超的《RethinkDB-&-TokuDB调研测试报告.pptx》

关于TokuDB和InnoDB性能压测报告,可参考:

以下安装基于Percona 5.6.31-77.0

安装libjemalloc库
该库在EPEL中有提供
# wget http://dl.fedoraproject.org/pub/epel/epel-release-latest-6.noarch.rpm
# rpm -ivh epel-release-latest-6.noarch.rpm 
# yum install jemalloc -y

通过yum安装,生成的库文件为/usr/lib64/libjemalloc.so.1
复制代码
[root@localhost ~]# rpm -qa |grep jemalloc
jemalloc-3.6.0-1.el6.x86_64
[root@localhost ~]# rpm -ql jemalloc-3.6.0-1.el6.x86_64
/usr/bin/jemalloc.sh
/usr/lib64/libjemalloc.so.1
/usr/share/doc/jemalloc-3.6.0
/usr/share/doc/jemalloc-3.6.0/COPYING
/usr/share/doc/jemalloc-3.6.0/README
/usr/share/doc/jemalloc-3.6.0/VERSION
/usr/share/doc/jemalloc-3.6.0/jemalloc.html
复制代码

修改配置文件my.cnf
在[mysqld_safe]下设置malloc-lib变量
malloc-lib=/usr/lib64/libjemalloc.so.1
重启mysqld_safe进程
启动过程中,会输出以下信息:
160810 20:11:46 mysqld_safe Adding '/usr/lib64/libjemalloc.so.1' to LD_PRELOAD for mysqld

如果不安装该库的话,则在加载TokuDB插件时会报如下错误:
mysql> INSTALL PLUGIN tokudb SONAME 'ha_tokudb.so';
2016-08-10 16:25:02 1861 [ERROR] TokuDB is not initialized because jemalloc is not loaded
2016-08-10 16:25:02 1861 [ERROR] Plugin 'TokuDB' init function returned error.
2016-08-10 16:25:02 1861 [ERROR] Plugin 'TokuDB' registration as a STORAGE ENGINE failed.
2016-08-10 16:25:02 1861 [Note] Shutting down plugin 'TokuDB'
ERROR 1123 (HY000): Can't initialize function 'tokudb'; Plugin initialization function failed.

禁用Transparent huge pages
查看当前内核是否已启用Transparent huge pages
# cat /sys/kernel/mm/transparent_hugepage/enabled
[always] madvise never
always代表已启用

如何禁用呢?
echo never > /sys/kernel/mm/transparent_hugepage/enabled
echo never > /sys/kernel/mm/transparent_hugepage/defrag

如果没有禁用的话,在后续安装插件的时候会报错:
mysql> INSTALL PLUGIN tokudb SONAME 'ha_tokudb.so';
ERROR 1123 (HY000): Can't initialize function 'tokudb'; Plugin initialization function failed.
并且,错误日志中打印如下信息:
复制代码
2016-08-10 20:47:51 4764 [ERROR] TokuDB: Huge pages are enabled, disable them before continuing

2016-08-10 20:47:51 4764 [ERROR] ************************************************************
2016-08-10 20:47:51 4764 [ERROR]                                                             
2016-08-10 20:47:51 4764 [ERROR]                         @@@@@@@@@@@                         
2016-08-10 20:47:51 4764 [ERROR]                       @@'         '@@                       
2016-08-10 20:47:51 4764 [ERROR]                      @@    _     _  @@                      
2016-08-10 20:47:51 4764 [ERROR]                      |    (.)   (.)  |                      
2016-08-10 20:47:51 4764 [ERROR]                      |    (.)   (.)  |                      
2016-08-10 20:47:51 4764 [ERROR]                      |             ` |                      
2016-08-10 20:47:51 4764 [ERROR]                      |        >    ' |                      
2016-08-10 20:47:51 4764 [ERROR]                      |     .----.    |                      
2016-08-10 20:47:51 4764 [ERROR]                      ..   |.----.|  ..                      
2016-08-10 20:47:51 4764 [ERROR]                       ..  '      ' ..                       
2016-08-10 20:47:51 4764 [ERROR]                         .._______,.                         
2016-08-10 20:47:51 4764 [ERROR]                                                             
2016-08-10 20:47:51 4764 [ERROR] TokuDB will not run with transparent huge pages enabled.        
2016-08-10 20:47:51 4764 [ERROR] Please disable them to continue.                            
2016-08-10 20:47:51 4764 [ERROR] (echo never > /sys/kernel/mm/transparent_hugepage/enabled)  
2016-08-10 20:47:51 4764 [ERROR]                                                             
2016-08-10 20:47:51 4764 [ERROR] ************************************************************
2016-08-10 20:47:51 4764 [ERROR] Plugin 'TokuDB' init function returned error.
2016-08-10 20:47:51 4764 [ERROR] Plugin 'TokuDB' registration as a STORAGE ENGINE failed.
2016-08-10 20:47:51 4764 [Note] Shutting down plugin 'TokuDB'
复制代码

启用TokuDB
Percona是5.6.17-66.0将TokuDB插件引入到自己的二进制版本中,5.6.19-67.0起才能作为GA版本使用。5.6.22-72.0起开发了一个脚本ps_tokudb_admin来简化TokuDB的安装工作。

首先,来看看TokuDB如何手动安装
主要是加载插件,TokuDB插件名为ha_tokudb.so,默认放到plugin_dir下。
登录mysql客户端,执行如下命令
复制代码
INSTALL PLUGIN tokudb SONAME 'ha_tokudb.so';
INSTALL PLUGIN tokudb_file_map SONAME 'ha_tokudb.so';
INSTALL PLUGIN tokudb_fractal_tree_info SONAME 'ha_tokudb.so';
INSTALL PLUGIN tokudb_fractal_tree_block_map SONAME 'ha_tokudb.so';
INSTALL PLUGIN tokudb_trx SONAME 'ha_tokudb.so';
INSTALL PLUGIN tokudb_locks SONAME 'ha_tokudb.so';
INSTALL PLUGIN tokudb_lock_waits SONAME 'ha_tokudb.so';
INSTALL PLUGIN tokudb_background_job_status SONAME 'ha_tokudb.so';
复制代码

查看插件是否加载成功
mysql> show engines;
| TokuDB             | YES     | Percona TokuDB Storage Engine with Fractal Tree(tm) Technology             | YES          | YES  | YES        |
mysql> show plugins;
复制代码
| TokuDB                        | ACTIVE   | STORAGE ENGINE     | ha_tokudb.so | GPL     |
| TokuDB_file_map               | ACTIVE   | INFORMATION SCHEMA | ha_tokudb.so | GPL     |
| TokuDB_fractal_tree_info      | ACTIVE   | INFORMATION SCHEMA | ha_tokudb.so | GPL     |
| TokuDB_fractal_tree_block_map | ACTIVE   | INFORMATION SCHEMA | ha_tokudb.so | GPL     |
| TokuDB_trx                    | ACTIVE   | INFORMATION SCHEMA | ha_tokudb.so | GPL     |
| TokuDB_locks                  | ACTIVE   | INFORMATION SCHEMA | ha_tokudb.so | GPL     |
| TokuDB_lock_waits             | ACTIVE   | INFORMATION SCHEMA | ha_tokudb.so | GPL     |
| TokuDB_background_job_status  | ACTIVE   | INFORMATION SCHEMA | ha_tokudb.so | GPL     |
复制代码

创建一张表,测试一下
mysql> create table test.test(id int) engine=tokudb;
Query OK, 0 rows affected (0.23 sec)
至此,TokuDB手动安装完毕~

脚本安装
其实,Percona官方在5.6.22-72.0版本中提供了一个脚本,可用于自动安装TokuDB插件,涉及的操作包括禁用透明大页,加载插件。
该脚本在二进制包的bin目录下。
复制代码
[root@localhost bin]# ./ps_tokudb_admin --help
This script is used for installing and uninstalling TokuDB plugin for Percona Server 5.6.
It can also be used to install or uninstall the Percona TokuBackup plugin (requires mysql server restart).
If transparent huge pages are enabled on the system it adds thp-setting=never option to my.cnf
to disable it on runtime.

Valid options are:
  --user=user_name, -u user_name     mysql admin username
  --password[=password], -p[password]     mysql admin password (on empty will prompt to enter)
  --socket=path, -S path         the socket file to use for connection
  --host=host_name, -h host_name     connect to given host
  --port=port_num, -P port_num         port number to use for connection
  --defaults-file=file          specify defaults file instead of guessing
  --enable, -e                 enable TokuDB plugin and disable transparent huge pages in my.cnf
  --enable-backup, -b             enable Percona TokuBackup and add preload-hotbackup option to my.cnf
                     (this option includes --enable option)
  --disable, d                 disable TokuDB plugin and remove thp-setting=never option in my.cnf
                     (this option includes --disable-backup option)
  --disable-backup, r             disable Percona TokuBackup and remove preload-hotbackup option in my.cnf
  --help                 show this help

For TokuDB requirements and manual steps for installation please visit this webpage:
http://www.percona.com/doc/percona-server/5.6/tokudb/tokudb_installation.html
复制代码
该脚本是用shell写的,参数也比较简单,其中,--enable-backup还包括安装TokuDB在线热备插件。

脚本安装过程如下
复制代码
 ./ps_tokudb_admin --enable --host=127.0.0.1 --port=3307 --user=root -p
Enter password:
Continuing without password...


Checking SELinux status...
INFO: SELinux is disabled.

Checking if Percona Server is running with jemalloc enabled...
INFO: Percona Server is running with jemalloc enabled.

Checking transparent huge pages status on the system...
INFO: Transparent huge pages are currently disabled on the system.

Checking if thp-setting=never option is already set in config file...
INFO: Option thp-setting=never is set in the config file.

Checking TokuDB engine plugin status...
INFO: TokuDB engine plugin is installed.
复制代码

总结
TokuDB最新版本是7.5.7,实际上现在是以插件的形式包含在Percona二进制发行版中。但该插件并不能直接在MySQL社区版中使用。
TokuDB基于MySQL 5.5.30源码,重新打包了个二进制版本,mysql-5.5.30-tokudb-7.1.0-linux-x86_64.tar.gz,基于TokuDB 7.1.0。
但也仅限如此,MySQL 5.6和5.7的社区版中并不能直接使用TokuDB插件。
以后若要使用较新版本的MySQL和TokuDB,只能使用Percona版本的。
下面是官档中的说明
Please note that Tokutek made some changes to the MySQL source that are required to either fix bugs or aid in performance, so if you are compiling from source you must use the Tokutek version of MySQL that is based on the MySQL 5.5.30 source.

参考

ptmalloc,tcmalloc和jemalloc内存分配策略研究

    最近看了glibc的ptmaoolc,Goolge的tcmalloc和jemalloc,顺便做了一点记录。可能有些地方理解地不太对,如有发现还请大神指出。

操作系统内存布局
    各种malloc的内存分配管理方式离不开操作系统的内存布局策略。
32位经典内存布局

    32位系统下经典内存布局如上,程序起始的1GB地址为内核空间,接下来是向下增长的栈空间和由0x40000000向上增长的mmap地址。而堆地址是从底部开始,去除ELF、数据段、代码段、常量段之后的地址并向上增长。但是这种布局有几个问题,首先是容易遭受溢出攻击;其次是,堆地址空间只有不到1G有木有?如果mmap内存比较少地址很浪费有木有?所以后来就有了另一种内存布局。
32位默认内存布局

    这幅图描述地比较清楚也比较完整。首先是加入了几种Random offset随机偏移,导致内存溢出攻击不那么容易了,其次是堆仍然向上,但是mmap向下增长。但是这样的话栈空间就不是动态增长的了,所以现在的操作系统都有栈大小限制来着(Windows好像默认是2MB对吧?Linux可以ulimit –s查看)。这种内存布局地址利用度比较高。

64位内存布局

    64位系统的寻址空间比较大,所以仍然沿用了32位的经典布局,但是加上了随机的mmap起始地址,以防止溢出攻击。反正一时半会是用不了这么大的内存地址了,所以至少N多年不会变了(话说要生产出40TB+的内存条,把堆内存地址用光,一时半会也搞不定吧)。
总结
    纵观各种内存布局,对于大内存各种malloc基本上都是直接mmap的。而对于小数据,则通过向操作系统申请扩大堆顶,这时候操作系统会把需要的内存分页映射过来,然后再由这些malloc管理这些堆内存块,减少系统调用。而在free内存的时候,不同的malloc有不同的策略,不一定会把内存真正地还给系统,所以很多时候,如果访问了free掉的内存,并不会立即Run Time Error,只有访问的地址没有对应的内存分页,才会崩掉。
Ptmalloc
    Ptmalloc采用主-从分配区的模式,当一个线程需要分配资源的时候,从链表中找到一个没加锁的分配区,在进行内存分配。

小内存分配
    在ptmalloc内部,内存块采用chunk管理,并且将大小相似的chunk用链表管理,一个链表被称为一个bin。前64个bin里,相邻的bin内的chunk大小相差8字节,称为small bin,后面的是large bin,large bin里的chunk按先大小,再最近使用的顺序排列,每次分配都找一个最小的能够使用的chunk。

    Chunk的结构如上所示,A位表示是不是在主分配区M表示是不是mmap出来滴P表示上一个内存紧邻的chunk是否在使用,如果没在使用,则size of previous chunk是上一个chunk的大小,否则无意义(而且被用作被分配出去的内存了),正式根据P标记位和size of previous chunk在free内存块的时候来进行chunk合并的。当然,如果chunk空闲,mem里还记录了一些指针用于索引临近大小的chunk的,实现原理就不复述了,知道大致作用就行。

    在free的时候,ptmalloc会检查附近的chunk,并尝试把连续空闲的chunk合并成一个大的chunk,放到unstored bin里。但是当很小的chunk释放的时候,ptmalloc会把它并入fast bin中。同样,某些时候,fast bin里的连续内存块会被合并并加入到一个unsorted bin里,然后再才进入普通bin里。所以malloc小内存的时候,是先查找fast bin,再查找unsorted bin,最后查找普通的bin,如果unsorted bin里的chunk不合适,则会把它扔到bin里。

大内存分配
    Ptmalloc的分配的内存顶部还有一个top chunk,如果前面的bin里的空闲chunk都不足以满足需要,就是尝试从top chunk里分配内存。如果top chunk里也不够,就要从操作系统里拿了。

    还有就是特别大的内存,会直接从系统mmap出来,不受chunk管理,这样的内存在回收的时候也会munmap还给操作系统。

简而言之,就是:
小内存: [获取分配区(arena)并加锁] -> fast bin -> unsorted bin -> small bin -> large bin -> top chunk -> 扩展堆
大内存: 直接mmap


总结 
    释放的时候,几乎是和分配反过来,再加上可一些chunk合并和从一个bin转移到另一个bin的操作。并且如果顶部有足够大的空闲chunk,则收缩堆顶并还给操作系统。

    介于此,对于ptmalloc的内存分配使用有几个注意事项
  1. Ptmalloc默认后分配内存先释放,因为内存回收是从top chunk开始的。
  2. 避免多线程频繁分配和释放内存,会造成频繁加解锁。
  3. 不要分配长生命周期的内存块,容易造成内碎片,影响内存回收。 

Tcmalloc
    Ptmalloc在性能上还是存在一些问题的,比如不同分配区(arena)的内存不能交替使用,比如每个内存块分配都要浪费8字节内存等等,所以一般倾向于使用第三方的malloc。
    Tcmalloc是Google gperftools里的组件之一。全名是 thread cache malloc(线程缓存分配器)其内存管理分为线程内存中央堆两部分。

小内存分配
    对于小块内存分配,其内部维护了60个不同大小的分配器(实际源码中看到的是86个),和ptmalloc不同的是,它的每个分配器的大小差是不同的,依此按8字节、16字节、32字节等间隔开。在内存分配的时候,会找到最小复合条件的,比如833字节到1024字节的内存分配请求都会分配一个1024大小的内存块。如果这些分配器的剩余内存不够了,会向中央堆申请一些内存,打碎以后填入对应分配器中。同样,如果中央堆也没内存了,就向中央内存分配器申请内存。

    在线程缓存内的60个分配器(文档上说60个,但是我在2.0的代码里看到得是86个)分别维护了一个大小固定的自由空间链表,直接由这些链表分配内存的时候是不加锁的。但是中央堆是所有线程共享的,在由其分配内存的时候会加自旋锁(spin lock)。

    在线程内存池每次从中央堆申请内存的时候,分配多少内存也直接影响分配性能。申请地太少会导致频繁访问中央堆,也就会频繁加锁,而申请地太多会导致内存浪费。在tcmalloc里,这个每次申请的内存量是动态调整的,调整方式使用了类似把tcp窗口反过来用的慢启动(slow start)算法调整max_length, 每次申请内存是申请max_length和每个分配器对应的num_objects_to_move中取小的值的个数的内存块。

    num_objects_to_move取值比较简单,是以64K为基准,并且最小不低于2,最大不高于32的值。也就是说,对于大于等于32K的分配器这个值为2(好像没有这样的分配器 class),对于小于2K的分配器,统一为32。其他的会把数据调整到64K / size 的个数。(可能是经验数值,目前2.0版本里的代码是这么写的)

    对于max_length就比较复杂了,而且其更多是用于释放内存。max_length由1开始,在其小于num_objects_to_move的时候每次累加1,大于等于的时候累加num_objects_to_move。释放内存的时候,首先max_length会对齐到num_objects_to_move,然后在大于num_objects_to_move的释放次数超过一定阀值,则会按num_objects_to_move缩减大小。


大内存分配
    对于大内存分配(大于8个分页, 即32K),tcmalloc直接在中央堆里分配。中央堆的内存管理是以分页为单位的,同样按大小维护了256个空闲空间链表,前255个分别是1个分页、2个分页到255个分页的空闲空间,最后一个是更多分页的小的空间。这里的空间如果不够用,就会直接从系统申请了。

分页管理 – span
    Tcmalloc使用一种叫span的东东来管理内存分页,一个span可以包含几个连续分页。一个span的状态只有未分配(这时候在空闲链表中),作为大对象分配,或作为小对象分配(这时候span内记录了小对象的class size)。
    在32位系统中,span分为两级由中央分配器管理。第一级有2^5个节点,第二级是2^15个。32位总共只能有2^20个分页(每个分页4KB = 2^12)。(骗纸,我在代码里明明看到的是2^7和2^13,定义了TCMALLOC_LARGE_PAGES宏之后才是 2^5和2^15,可是所有的代码或者编辑脚本里都没定义这个宏,可能是文档没更新)
在64为系统中,有三级。

    在资源释放的时候,首先计算其分页编号,然后再查找出它对应的span,如果它是一个小对象,则直接归入小对象分配器的空闲链表。等到空闲空间足够大以后划入中央堆。如果是大对象,则会把物理地址连续的前后的span也找出来,如果空闲则合并,并归入中央堆中。

    而线程缓存内的分配器,会根据max_length、num_objects_to_move和上一次垃圾收集到现在为止的最小链表长度,按一定的策略回收资源到中央堆中,具体的算法不再复述tcmalloc的文档写得比较清楚。同样可以在需要时减少某一个线程的max_length来转移内存,但是要等到那个线程下一次执行free,触发垃圾回收之后才会真正把内存返还中央堆。

简而言之,就是: 
小内存: 线程缓存队列 -> 中央堆 -> 中央页分配器(从系统分配)
大内存: 中央堆 -> 向系统请求

    Tcmalloc的管理策略和ptmalloc有很大区别,理论上性能提高的主要原因在线程缓存不加锁和少量操作的自旋锁上。不过按照它的实现方式,不适合多线程频繁分配大于8个分页(32KB)的内存。否则自旋锁争用会相当厉害,不过这种情况也比较少。而减少和中央堆交互又依赖于他的线程缓存长度自适应算法。
    还有就是它使用了外部的数据结构来管理span list,这样不会每次分配内存都要浪费header的长度。但是他的对齐操作又比ptmalloc多浪费了一些内存。(有点空间换时间的意思)
    所以无论是ptmalloc还是tcmalloc都应该尽量减少大内存的分配和释放。尽量先分配、后释放。


Jemalloc
    最后来看看第三个神器,jemalloc。这是FreeBSD、NetBSD和Firefox的默认malloc。据作者说,在高CPU核心数的情况下比tcmalloc性能还好。
    Jemalloc的设计目标是:
  1. 快速分配和回收
  2. 低内存碎片
  3. 支持堆性能分析
    Jemalloc 把内存分配分为了三个部分,第一部分类似tcmalloc,是分别以8字节、16字节、64字节等分隔开的small class;第二部分以分页为单位,等差间隔开的large class;然后就是huge class。内存块的管理也通过一种chunk进行,一个chunk的大小是2^k (默认4 MB)。通过这种分配实现常数时间地分配small和large对象,对数时间地查询huge对象的meta(使用红黑树)。
    默认64位系统的划分方式如下:
    Small: [8], [16, 32, 48, …, 128], [192, 256, 320, …, 512], [768, 1024, 1280, …, 3840]
    Large: [4 KiB, 8 KiB, 12 KiB, …, 4072 KiB]
    Huge: [4 MiB, 8 MiB, 12 MiB, …]

    Jemalloc也使用了分配区(arena)来维护内存。线程按第一次分配small或者large内存请求的顺序Round-Robin地选择一个分配区。每个分配区都维护了一系列分页,来提供small和large的内存分配请求。并且从一个分配区分配出去的内存块,在释放的时候一定会回到该分配区。
内存分配

    每个分配区内都会包含meta信息,记录了其对应的chunk列表,每个chunk的头部都记录了chunk的分配信息。在使用某一个chunk的时候,会把它分割成很多个run,并记录到bin中。不同size的class对应着不同的bin,这点和前面两种分配器一样。在bin里,都会有一个红黑树来维护空闲的run,并且在run里,使用了bitmap来记录了分配状态
    和前面两种分配器不同的是,分配区会同时维护两组run的红黑树,一组是未被分配过内存(clean区域),另一组是回收的内存(dirty区域)。而且不是前面那两种分配器所使用的链表。
    同样,每次分配,都是选取最小且符合条件的内存块。但是优先从dirty区域查找。
    由于分配区的设计和ptmalloc差不多。在访问分配区的时候需要对其加锁,或者对某一个size的bin加粒度更小的锁。为了减少锁征用,这里又参照tcmalloc引入了线程缓存。并且其线程缓存的垃圾回收机制和tcmalloc一样,也是基于分配请求的频率自动调整的。
    线程缓存的结构就像一个简化版的arena,加了一些垃圾回收的控制信息。
简而言之,就是: 
小内存(small class): 线程缓存bin -> 分配区bin(bin加锁) -> 问系统要
中型内存(large class):分配区bin(bin加锁) -> 问系统要
大内存(huge class): 直接mmap组织成N个chunk+全局huge红黑树维护(带缓存)


总结
    Jemalloc设计上比前两个复杂地多,其内部使用了红黑树管理分页和内存块。并且对内存分配粒度分类地更细。这导致一方面比ptmalloc的锁争用要少,另一方面很多索引和查找都能回归到指数级别,方便了很多复杂功能的实现。而且在大内存分配上,内存碎片也会比tcmalloc少。但是也正是因为他的结构比较复杂,记录了很多meta,所以在分配很多小内存的时候记录meta数据的空间会略微多于tcmalloc。但是又不像ptmalloc那样每一个内存块都有一个header,而采用全局的bitmap记录状态,所以大量小内存的时候,会比ptmalloc消耗的额外内存小。
大总结
    看这些个分配器的分配机制,可见这些内存管理机制都是针对小内存分配和管理。对大块内存还是直接用了系统调用。所以在程序中应该尽量避免大内存的malloc/new、free/delete操作。另外这个分配器的最小粒度都是以8字节为单位的,所以频繁分配小内存,像int啊bool啊什么的,仍然会浪费空间。经过测试无论是对bool、int、short进行new的时候,实际消耗的内存在ptmalloc和tcmalloc下64位系统地址间距都是32个字节。大量new测试的时候,ptmalloc平均每次new消耗32字节,tcmalloc消耗8字节(我想说ptmalloc弱爆啦,而且tcmalloc)。所以大量使用这些数据的时候不妨用数组自己维护一个内存池,可以减少很多的内存浪费。(原来STL的map和set一个节点要消耗近80个字节有这么多浪费在这里了啊)
    而多线程下对于比较大的数据结构,为了减少分配时的锁争用,最好是自己维护内存池。单线程的话无所谓了,呵呵。不过自己维护内存池是增加代码复杂度,减少内存管理复杂度。但是我觉得,255个分页以下(1MB)的内存话,tcmalloc的分配和管理机制已经相当nice,没太大必要自己另写一个。
    另外,Windows下内存分配方式不知道,不同类型(int、short和bool)连续new的地址似乎是隔开的,可能是内部实现的粒度更小,不同size的class更多。测试10M次new的时候,debug模式下明显卡顿了一下,平均每次new的内存消耗是52字节(32位)和72字节(64位)[header更复杂?]。但是Release模式下很快,并且平均每次new的内存消耗是20字节(32位)和24字节(64位)。可以猜测VC的malloc的debug模式还含有挺大的debug信息。是不是可以得出他的header里,Release版本只有1个指针,Debug里有5个指针呢?
参考文献:
  1. glibc内存管理ptmalloc源代码分析 http://rdc.taobao.com/blog/cs/?p=1015
  2. tcmalloc http://gperftools.googlecode.com/svn/trunk/doc/tcmalloc.html
  3. Scalable memory allocation using jemalloc http://www.facebook.com/notes/facebook-engineering/scalable-memory-allocation-using-jemalloc/480222803919
  4. A Scalable Concurrent malloc(3) Implementation for FreeBSD http://people.freebsd.org/~jasone/jemalloc/bsdcan2006/jemalloc.pdf
  5. jemalloc原理分析 http://club.alibabatech.org/article_detail.htm?articleId=36
  6. Tcmalloc 2.0源码
  7. Jemalloc 3.4.0 源码