背景:当尝试使用C++进行代码优化时,可以考虑从语言层次和设计层次进行性能优化。
1)语言层次:语言层次可以考虑从形参传递方式和返回值类型以及异常类型进行控制。
- 按引用传递:由于C++按值传递会有一定的复制开销,而按引用可以避免复制开销,如果不希望改变引用值,建议进行const修饰;对于复杂对象的创建(销毁),按值引用可能带来不必要地调用其他成员变量的构造函数(析构函数)
- 按引用返回:同样从函数的返回的角度考虑,按引用传递能避免不必要的复制开销;自C++11之后支持移动语义,允许高效地使用按值返回对象;
- 按引用捕捉异常:通过引用捕捉异常,以避免不必要的开销;通常抛出异常的开销很大,考虑使用引用,提升可能出现异常的情况;
- 恰当使用内联方法和函数:内联方法和函数的代码直接插入到被调用的位置,避免了函数调用的开销;由于内联函数的请求由编译器确定是否执行,只有熟悉一定的编译器文档才能恰当使用inline关键词,提升代码性能;
2)设计层次:
- 尽可能使用已有的数据结构和算法,尤其是STL标准库;
- 尽可能多的缓存:如果任务或计算执行特别慢,应该确保不会执行不必要的重复计算;将将来可能使用到的数据缓存下来,为下一次提供支持;可能存在缓存不同步问题(缓存失效),需要有一定的检测规则,确保缓存更新;
- 使用对象池:类似缓存原理,对需要经常和大量访问的对象设计对象池,实现一次创建,多次重复调用,从而避免不必要的构造函数和析构函数调用;
设计一个泛型对象池,创建一个大小为10的对象池,重复调用对象100次,记录观察构造函数和析构函数的调用次数;
// ObjectPool.h
#pragma once
#include<queue>
#include<memory>
template<typename T>
class ObjectPool{
public:
// constructor for default chunszie;
ObjectPool(size_t chunkSize = kDefaultChunkSize);
// prevent assignment and pass-by-value
ObjectPool(const ObjectPool<T>& src) = delete;
ObjectPool<T>& operator=(const ObjectPool<T>& rhs) = delete;
using Object = std::shared_ptr<T>;
// acuquire the pointer of queue.front()
Object acquireObject();
private:
std::queue<std::unique_ptr<T>> mFreeList;
size_t mChunkSize;
static const size_t kDefaultChunkSize = 10;
// fill the queue with mChunksize default value
void allocateChunk();
};
template<typename T>
const size_t ObjectPool<T>::kDefaultChunkSize;
template<typename T>
ObjectPool<T>::ObjectPool(size_t chunkSize){
if(chunkSize == 0){
throw std::invalid_argument("chunk size must be positive. ");
}
mChunkSize = chunkSize;
allocateChunk();
}
template<typename T>
void ObjectPool<T>::allocateChunk(){
for(size_t i = 0; i < mChunkSize; ++i){
mFreeList.emplace(std::make_unique<T>());
}
}
template<typename T>
typename ObjectPool<T>::Object ObjectPool<T>::acquireObject(){
if(mFreeList.empty()){
allocateChunk();
}
std::unique_ptr<T> obj(std::move(mFreeList.front()));
mFreeList.pop();
Object smartObject(obj.release(), [this](T* t){mFreeList.push(std::unique_ptr<T>(t));});
return smartObject;
}
测试用例
// main.cpp
#include<iostream>
#include"objectPool.h"
using namespace std;
class UserRequest{
public:
UserRequest(){mID = ++msCount; cout << "constructor : " << mID << endl;}
virtual ~UserRequest(){cout << "destructor : " << mID << endl;}
private:
int mID;
static int msCount;
};
int UserRequest::msCount = 0;
ObjectPool<UserRequest>::Object obtainUserRequest(ObjectPool<UserRequest>& pool){
auto request = pool.acquireObject();
return request;
}
void processUserRequest(ObjectPool<UserRequest>::Object& req){
return req.reset();
}
int main(){
ObjectPool<UserRequest> requestPool(10);
for(size_t i = 0; i < 100; ++i){
auto req = obtainUserRequest(requestPool);
processUserRequest(req);
}
cout << "done. " << endl;
return 0;
}
结果:
测试结果显示,尽管对象被调用了100,而对象池只是重复创建和销毁对象,只调用了10次构造函数和析构函数。注意设计中share_ptr和unique_ptr的使用情况,在队里中使用共享指针,每次请求都是从队列前端取对象发送给请求方。