最近一直找项目的瓶颈,并对它做优化。做了一个多月,找到了项目的瓶颈,但无从下手去改进。虽然没有什么进展,但还是学到了不少东西。在这里对新体会的一些东西做一个记录。
项目是一个服务器端程序,同大多数服务器程序相同,它的工作主要是完成如下任务
收消息->解码->处理消息->编码 ->发消息。
其中项目为了适应多个产商的不同消息扩展,对解码后的消息再次做了转换,转换为内部的统一的格式。相当于进行了二次解码。处理完后,组成内部发送消息格式,再转换成对应产商的消息格式,编码发送。
1.项目中用到了大量的STL库中的容器如vector,string,list,queue;
gcc的STL库,随版本不同,实现方式也有相当大的不同。随之的性能表现也各有不同。gcc-3.3.2中allocator默认采用内存池的方式管理内存。而在gcc-4.1.2中采用直接向系统申请内存的方式管理内存。同时提供pool_alloc(同gcc-3.3.2的默认allocator相同),mt_alloc(多线程下的内存分配器);
STL库的容器在使用时,会自动的管理内存,所以在使用时需要小心。会隐式的操作内存。向系统直接申请内存时,程序会发生上下文切换,需要频繁进入内核态,并存在加锁解锁的操作。在多线程下,频繁的申请内存会对系统的性能造成很大的影响。在必要时,需要对内存进行单独的管理。
string的使用:stl中string中遵循COW原则,采用引用计数方式来减少内存的申请复制。为了实现多线程安全的string,引用计数中采用原子锁来进行原子的加减操作。在多线程程序中也有可能影响性能。
2.项目中锁的使用
加锁的原则
根据要加锁区域的工作量,选择适当的锁。
spinlock 自旋锁 如果线程发现锁被别的线程占用,线程并不休眠,而是不停的轮询。适合加锁区计算量很小的任务。优点:减少了线程切换的消耗。缺点:cpu利用率低。
mutex 互斥锁 如果锁被占用,线程进入阻塞队列等待锁。优点:cpu利用率高。缺点:需要进行线程切换。
3.生产者/消费者 中消息队列
在服务器程序中大多采用这种模型来完成异步的消息处理。 生产者负责收消息,消费者来完成处理。它们通过一个消息队列来完成通信。
为了保证线程安全,消息队列通常需要加锁。加锁会造成程序的阻塞,引起线程切换的开销。可以通过双缓冲队列来减少生产者与消费者争用同一个锁的次数(原理同多线程中内存管理的方式相类似)。
4.线程池的模型与分配机制
leader/fellower
4.消息编解码的处理
服务器程序有很大一部分开销消耗在消息的编解码上 。尤其是消息的格式多变的情况下,如何高效的编码解码也是一个应该关注的问题。。
以上,是在分析性能过程中自己想到的,或参考别人的思路。没有经过验证,接下来的我将对这几种情况对性能的影响做比较,分析它们的适用环境。