编写正常运行的程序很容易,但一旦数据量大起来,对代码的性能就需要认真考虑了。对于服务器来说,如果客户端一次访问,就需要话费几百毫秒,那么一旦每秒的访问次数躲起来,前端就会造成很明显的延迟。严重影响用户体验。最近做后端服务也遇到了一些需要优化的问题。
性能优化的前提是良好的构架设计:如果架构本身的设计就存在问题,在怎么优化也所能提升的空间也是很小。根据二八原则,大部分性能应该消耗在很少的地方,所以优化的关注点就在于那20%最耗时的代码块,解决了这个问题,整体性能就会有很大的提升。
代码层面的优化一般涉及到一下方面:
1.并发模型的选择
对于不同的功能需要用不同的并发模型,适合单线程还是多线程,以及线程池这些都会影响到性能,对于服务器,模块是io密集型还是计算密集型。例如如果一次请求是需要做很多的计算,那么用线程池是合理的能够简化编程,但是如果一次请求主要时间是等待io,那么线程池是无法提升吞吐量的。
2.数据的存储
容器的选择
数据对象的存储方式取决于对数据的使用方式,就拿C++容器来说,如果要存储物品信息,一般系统中更多的是根据物品id来获取物品信息,那么久需要用key-value来存取,但map是红黑树,unorder_map是哈希表,哈希表的查询时间是O(1),而对于map,插入、删除都是O(logN),最坏和平均都是。
存取方式
是存指针还是存数据,这个需要根据具体问题,具体分心。
3.算法
算法的存在就是为了高效的解决一些复杂问题,以减少时间或者空间复杂度。一个排序就有很多种选择,冒泡,选择,快排,堆排序,堆排序,归并等。一般来说快排的整体效率比较高,那是针对于基本无序的序列,如果只是后来的元素无序,前面基本有序,这时候简单的使用快排就会造成降低性能。
4.代码质量
使用引用
在C++中尽量采用引用传递或者指针传递,这样不会产生参数拷贝,如果传入参数是一个复杂的数据结构,就能明显的提升性能。同理获取数据时也要用引用。
减少副作用
例如C++是不进行边界检查的,对于map的获取value,如果该key不在map中,map会插入一个key,value为默认值
std::map<int,int> i_map;
int value = i_map[123];
map是红黑树实现的,红黑树最终是一平衡树,这个插入操作会导致原来的树不平衡,内部就需要翻转达到平衡,这就会造成性能消耗。
同一变量的多次获取:
std::vector<int> v_int;
...
for (int i = 0; i < v_int.size(); i++)
怎么写出高质量的C++代码,推荐一本书 《高质量C++/C编程指南》。
同步锁
服务器编程会涉及到多线程,一旦涉及多线程,多线程中很多涉及到竞争资源的非原子操作都需要进行加锁,加锁的方式有很多种有信号量,互斥器,条件变量,读写锁。绝大多数情况使用互斥器就可以足够实现大多数功能,而且相对于其他锁性能影响较小。但在使用mutex时也需要注意。
- 锁的范围:只在需要同步的操作前后加锁和解锁,避免造成其他线程的无效的等待,造成性能损失。
- 尽量将操作划分,对于多种无前后依赖的临界资源,用多个互斥器