2012年tcmalloc学习笔记之四
1.什么时候进行线程缓存垃圾收集,需要满足什么条件
某个线程缓存当缓存中所有对象的总共大小超过2MB的时候,会对他进行垃圾收集。
2.线程缓存自由列表与中央自由列表的联系与区别;
我们会遍历缓存中所有的自由列表并且将一定数量的对象从自由列表移到对于得中央列表中。
3.为什么不会因为程序有大量线程而过度浪费内存?
垃圾收集阈值会自动根据线程数量的增加而减少。
4.自由列表中移除的对象的数量是如何确定的?
从某个自由列表中移除的对象的数量是通过使用一个每列表的低水位线<code>L</code>来确定的。<code>L</code>记录了自上一次垃圾收集以来列表最短的长度。注意,在上一次的垃圾收集中我们可能只是将列表缩短了<code>L</code>个对象而没有对中央列表进行任何额外访问。我们利用这个过去的历史作为对未来访问的预测器并将<code>L/2</code>个对象从线程缓存自由列表中移到相应的中央自由列表中。这个算法有个很好的特性是,如果某个线程不再使用某个特定的尺寸时,该尺寸的所有对象都会很快从线程缓存被移到中央自由列表,然后可以被其他缓存利用。
环境变量LD_PRELOAD指定程序运行时优先加载的动态连接库,这个动态链接库中的符号优先级是最高的。
标准C的各种函数都是存放在libc.so的文件中,在程序运行时自动链接。使用LD_PRELOAD后,自己编写的malloc的加载顺序高于glibc中的malloc,这样就实现了替换。
<code>g++hack.cpp -shared -fPIC -o hack.so</code>
<code>LD_PRELOAD="./hack.so"./checkpasswd abc</code>
<code>只需添加链接选项</code><code>-ltcmalloc</code><code>即可</code>
<code>g++wrap.cpp -o wrap -Wl,-wrap,malloc</code>
ld中有一个选项–wrap,当查找某个符号时,它优先先解析__wrap_symbol, 解析不到才去解析symbol。例如:
<code>1</code>
<code>void</code><code>*__wrap_malloc (size_t</code>
<code>c)</code>
<code>2</code>
<code>{</code>
<code>3</code>
<code> </code><code>printf</code><code>("malloc called with %u\n", c);</code>
<code>4</code>
<code> </code><code>return</code><code>__real_malloc (c);</code>
<code>5</code>
<code>}</code>
当其它文件与你实现__wrap_malloc函数的文件链接时使用–wrapmalloc,则所有到malloc的调用都是会链接到__wrap_malloc上。只有调用__reall_malloc时才会调用真正的malloc
<code>#include<stdio.h></code>
<code>#include<stdlib.h></code>
<code>extern</code><code>"C"</code>
<code>void</code><code>*__real_malloc(size_t);</code>
<code>void</code><code>*__real_free(void</code> <code>*);</code>
<code>6</code>
<code>7</code>
<code>void</code><code>*__wrap_malloc(size_t</code>
<code>8</code>
<code>9</code>
<code> </code><code>printf("MyMALLOC called: %d\n", c);</code>
<code>10</code>
<code> </code><code>return</code><code>__real_malloc(c);</code>
<code>11</code>
<code>12</code>
<code>void</code><code>*__wrap_free(void</code> <code>*ptr)</code>
<code>13</code>
<code>14</code>
<code> </code><code>printf("MyFREE called: 0x%08X\n", ptr);</code>
<code>15</code>
<code> </code><code>return</code><code>__real_free(ptr);</code>
<code>16</code>
<code>17</code>
<code>18</code>
<code>int</code><code>main (int</code>
<code>argc,char</code> <code>*argv[])</code>
<code>19</code>
<code>20</code>
<code> </code><code>void</code><code>*ptr = malloc(12);</code>
<code>21</code>
<code> </code><code>free(ptr);</code>
<code>22</code>
<code> </code><code>return</code><code>0;</code>
<code>23</code>
编译:
结果:
MyMALLOC called: 12
My FREE called: 0×00501010
<code>mysql</code>
<code>nginx</code>
<code>squid</code>
<code>专用的‘对象池’可以比通用的‘内存池’性能高两个数量级。</code><code></code><code>通过宏定义(</code><code>DECL_MEM_POOL,IMPL_MEM_POOL</code><code>)可以很快速的为</code><code>class</code><code>增加</code><code>pool</code><code>能力,还可以在单线程的环境下去掉锁。</code>
<code>tcmalloc</code><code>将内存请求分为两类,大对象请求和小对象请求,大对象为</code><code>>=32K</code><code>的对象。</code><code>| tcmalloc</code><code>会为每个线程分配线程局部缓冲</code><code></code><code>对于小对象请求,可以直接从线程局部缓冲区获取,如果线程局部缓冲区没有空闲内存,则从</code><code>centralheap</code><code>中一次性获取一连串小对象。</code><code>tcmalloc</code><code>对于小内存,按</code><code>8</code><code>的整数次倍分配,对于大内存,按</code><code>4K</code><code>的整数次倍分配。</code><code></code><code>这样做有两个好处,一是分配的时候比较快。二是短期的收益比较大,分配的小内存至多浪费</code><code>7</code><code>个字节,大内存则</code><code>4K</code>
<code>valgrind</code>
<code>./configure–enable-frame_pointers</code>
<code>./configure--enable-frame_pointers && make && sudo make install</code>
<code>sudoldconfig</code>
<code>g++.... -ltcmalloc (link static lib)</code>
<code>修改参数</code>
<code>tcmalloc</code><code>每个线程默认最大缓存</code><code>16M</code><code>空间,所以当线程多的时候其占用的空间还是非常可观的,在</code><code>common.h</code><code>中有几个参数是控制缓存空间的,可以做合理的修改(只可个人做实验,注意法律问题):</code>
<code>1.</code><code>降低每个线程的缓存空间,</code>可以修改common.h中的kMaxThreadCacheSize,比如2M
2.降低所有线程的缓存空间的总大小,可以修改common.h中的kDefaultOverallThreadCacheSize,比如20M
3.尽快将free的空间还给centrallist,可以将kMaxOverages改小一点,比如1
还可以定期让tcmalloc归还空间给OS,
<code>#include"google/malloc_extension.h"</code>
<code>sbrk</code><code>,</code><code>mmap</code><code>,</code><code>/dev/mem</code><code>文件</code>
<code>默认是三种都</code><code>try</code><code>的,一种不行换另外一种。</code>
<code>1.</code><code>释放某个</code><code>object</code>
2.找到该object所在的span
3.如果该span中所有object都被释放,则释放该span到对应的可用列表,在释放的过程中,尝试将该span跟左右spansmerge成更大的span
4.如果当前threadcache的free空间大于指定预置,归还部分空间给centrallist
5.central list也会试图通过释放可用span列表的最后几个span来将不用的空间归还给OS
tcmalloc向OS申请/释放资源是以span为单位的。
tcmalloc里面不少实现值得称道,比如pagesize到void*的mapping方式,添加/移除链表元素的时候利用结构体内存布局直接赋值,span/page/item的内存层次结构等,值得一看。
1.首先根据申请空间的大小从当前线程的可用内存块里面找(每个进程维护一组链表,每个链表代表一定大小的可用空间)
2.如果step1没有找到,则到centrallist里面查找(centrallist跟线程各自维护的list结构很像,为不同的size各自维护一组可用空间列表)
3.如果step2 central list也没有找到,则计算分配size个字节需要分配多少page(变量:class_to_pages)
4.根据pagemap查找page对应的可用的span列表,如果找到了,则直接返回span,centrallist会将该span切割成合适的大小放入对应的列表中,然后交给threadcache
5.如果step4没有找到可用的span,则向OS直接申请,然后步骤同step4。
<code>1</code><code>、</code><code>libunwind</code><code>的编译参数改为:</code><code>CFLAGS=-fPIC./configure make CFLAGS=-fPIC make CFLAGS=-fPIC install</code>
echo“/usr/local/lib” >/etc/ld.so.conf.d/usr_local_lib.conf
/sbin/ldconfig
echo“/usr/local/lib” > /etc/ld.so.conf.d/usr_local_lib.conf
<code>/sbin/ldconfig</code>