【原创】C++编写高性能服务器:内存分配篇

    服务器程序有别于一般应用程序,安全与性能应被给予较高的待遇。在这里我们只讨论性能相关的问题。Apache是一款开源,安全及高性能的服务器,我经常把它放在各个档次的机器上进行测试,它总是能以较低cpu占用率和较高的吞吐量完成测试,佩服佩服啊。以Apache做为性能的参照,我从前年年底开始着手开发自己的服务器,历时两年,终于以能提供与其近似的性能(还是相差5%-10%在较差的机器上,一台P450)完成,还没取名字,暂且叫它OpenHL。

    在设计上的不同处:
    Apache是用C语言开发的,处理请求是一条线程(进程)完成一个请求(堵塞方式)。
    OpenHL是用C++语言开发的,处理请求可以是前缀,异步及堵塞中任一方式,前缀就是Completion Port或aio了,异步是以select,poll等进行驱动。
    语言方面,选用C++,除了人方便了,剩下的都是机器的麻烦了(编译累,执行累等等)。

    编写自己的服务器:
    在我编写完第一个版本时,性能要比Apache差不少,当时才过夏,所以测试我的AMD免去温度的影响。令人安慰的是其它几款服务器表现更差,想想人家Apache是被公认,差个10%-20%是可以被原谅的,何况我用的C++编写的,语言上也造成一定的差距,一番自我安慰后开始沾沾自喜了。

    问题出现:
    今年夏初,我已经将OpenHL改为第二版,在结构上做了较大的改动。我又进行了测试,测试表明改动并未产生大的影响。直到一次测试出现了不正常的现象,OpenHL的cpu占用率彪升,从原来的平均%35,上升到%60,%70...%90。停止测试后一切正常,再进行,问题仍时不时的出现。相似的是问题总是在测试20-30秒时出现。

    首先能想到的是测试环境,由于夏天气温高,AMD就时有罢工。测试中Cpu会因为高温而不稳定,但不至于如此。如果Apache也有此现象就是机器的问题了,结果证明了Apache运行的很好。通过查看进程的各项指数,发现Apache的页面错误很低,而OpenHL却非常高,测试时每秒都在不停的攀升。页面错误是由于内存页未在物理内存中造成的,进程实际使用内存OpenHL要比Apache还少些。怪了怪了。

   
    问题所在:
    这个现象让我通过观察Apache源码发现,Apache是通过内存池来分配,并且每个请求都使用独立的内存池。内存池简单的分配较大内存块再切割成所需大小,这么一来一次请求实际所要从堆中分配次数也就三两次就足够了。再来瞧瞧OpenHL有多可怜,每个请求都要不停的分配大大小小的C++对象,还要不厌其烦的把它们送回家。其实不仅仅是内存分配所至,大量的分配回收带来了同步操作,同时增加了线程上下文切换,这才应该是关键。能不能学学Apache的内存池呢,答案是难,也不客观。可是应该有办法优化下我们的分配器吧,让我们看看MSDN中对此的描述,就是那篇《堆:快乐和痛苦》:Heap被如何优化了,性能有显著提升,再加上给出的优化技巧(把对象组合在一起,很明显我没有好办法把所有的字符串都拼在一起使用),就可以放心的分配内存了。

    开始优化:
    事实证明M$的Heap是一款很慢的分配器,慢的让你接受不了。我在网上找到几种分配器,dlmalloc,ptmalloc和hoard_malloc,后两个都是基于dlmalloc(Dong Lea)开发的。我现在用的是dlmalloc2.83,测试Heap和dlmalloc(单线程)基本不是一个档次的。我们的优化工作还没有完,单单靠分配器是不行的,不论哪款分配器在分配时仍需同步,不管它分配的再快,仍会产生很多线程切换。解决办法通过线程存储缓冲小对象,根据经验,只缓冲小于128个字节的对象,并且以16为间隔,共分8个档(在我的程序中以96做上限分6个档最好),命中率高达80%。缓冲大小为24k或32k,再高影响好象并不大。

    字符串分配:我是直接使用CStringT,由于服务中需要大量使用字符串处理,我并未让它直接从分配器中分配,同样利用线程存储缓冲,方法稍有变化,这里只分32, 64, 128, 256四个档,相互独立,上限均为(8K/size)个,个人认为分前三个档效果也不错。档与档间独立的可以不相互影响,而一般对象的内存使用上,大小尺度比较散乱,并且是定长,不象字符串常需拼接操作,多分几个档可以节约开销,但档次一多就得用一个总上限来限制,这样也能根据使用情况自动调整各个档应缓冲多少。字符串的命中率高达90%。

    再次测试:
    这次测试基本和Apache持平,在P450上也差的很少。当然还包含了其它的优化。起码再没出现过cpu占用率彪升的情况。页面错误很少,除了启动时的,只有几百到一两千次,比Apache的还要稍微少些。


    对于Heap系列函数的问题,我很不理解它在做什么,比如也HeapSize很慢,我曾建立过一个Heap,反复分配释放同样大小的内存10万次,结果是,所耗时间与内存大小成正比,16k的花了2.8秒,8k的花了1.4秒,1k的0.8-0.9秒....。而dlmalloc不管什么大小都只有50个毫秒。我的系统是Win2000 Server,AMD 2000+,内存512M。

    dlmalloc 最好的分配器之一,由Dong Lea开发的,开源。
    ptmalloc 实际是dlmalloc的多线程版,内置多个分配器与线程绑定,以减少同步影响。要比dlmalloc在多线中快不少,至于多少我还不知道,因为前端缓冲已经减少了绝大多数的分配操作。
    hoard_malloc 实际做法和我的差不多,不过我想它没有专门对字符串优化,应该差些。我没有用它。

    以后我会说说我在完成端口及重叠IO开发时的问题及解决,和一些经验。
    文科学的不太好,大家将就看看吧。

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值