最近相对比较有时间,于是想把代码整理一下,重构:永无止境.
去年的时候,SOCKET用的是ACE,因为是公司用了很长时间的一个框架, 也就没细想,直接拿来过来用。但虫虫和棋牌不同:服务器要处理的数据量完全不是一个级别,而公司包装过的ACE竟然使用的是阻塞模式。于是,一个风高月黑的晚上,服务器卡得动弹不得。为了查这个BUG,我将所有的比较复杂的函数都加了时间跟踪, 也就是跟踪这个函数执行倒的花了多少时间。
代码中有这么一个逻辑:扫描所有的房间,找出最合适的一个。一个服务器默认开1K个房间,这么简单逻辑应不至于太慢吧(默认执行时间0.001秒为报警下限),但倒的有多慢吧,我想,不会超过0.0001秒,于是,把报警下限设置成0.0001秒,也没多想,就更新到线上去了。
新版本上线,习惯会跟踪一下,结果让大吃一惊:单个服务器,一天之内收到了4W多条告警。
以下是我旧版的代码:
{
NET_TIME_TRACE_POINT(RoomMgr::MatchRoom,0);
int nLastLevelDiff = INT_MAX;
staticconst int MATCH_LEVEL_FACTOR = 2;
for(Rooms_type::iterator roomIt = m_RoomList.begin(); roomIt != m_RoomList.end();++roomIt)
{
if(excluderoom != (*roomIt)->GetID() && (*roomIt)->GetStatus() ==Room::ROOM_STATUS_HOLD && (*roomIt)->getRoomType() == roomtype&& (*roomIt)->getMaxPlayer() > (*roomIt)->GetPlayerCount())
{
constint nLevelDiff = abs((*roomIt)->getAvgLevel() - playerlevel);
if(nLevelDiff < MATCH_LEVEL_FACTOR)
{
//等级相差2级之内,直接匹配成功.
room= *roomIt;
break;
}
elseif (nLevelDiff < nLastLevelDiff)
{
//否则匹配一个等级差最近的房间.
nLastLevelDiff= nLevelDiff;
room= *roomIt;
}
}
}
//因为房间为空时,房间类型为 Room::ROOM_TYPE_2VS2.
if(room == NULL && roomtype == Room::ROOM_TYPE_1VS1)
{
room= this->GetEmptyRoom();
}
}
以下是在本地测试时的日志输出, 足足花了270ns, 我一直以为这样的函数,以目前CPU的运算能力,执行时间可以忽略不计。时间倒的花在什么地方呢?
<TIME_TRACEname='RoomMgr::MatchRoom' Span='0.000270'/>
Rooms_type 是一个 std::vector容器, std::vector迭代的速度是所有STL容器里面最快的,理应不会出问题,死马当活马医吧,于是,马上改写了一个数组版本,在本地服务器上一跑,很失望, std::vector 容器版本比数组版本要慢50ns左右, 但整个函数的开销还在200ns以上。同时也发现一个有趣的现像,std::vector 使用迭代器来迭代比反向迭代器要快,并且比使用下标的版本也要快。
1 . for (Rooms_type::size_type i = 0 ; i < m_RoomList.size();++i) //使用下标来迭代
2 . for(Rooms_type::reverse_iterator roomIt = m_RoomList.rbegin(); roomIt !=m_RoomList.rend(); ++roomIt) // 使用反向迭代器来迭代
3 . for (Rooms_type::iterator roomIt = m_RoomList.begin(); roomIt!= m_RoomList.end(); ++roomIt) //使用正向迭代器来迭代
如果迭代一个数组,什么也不做,只花了6ns的时间。天啦,200ns的时间难道有花在那么简单的几个函数调用和值比较上了?也就是下面这个语句花了那200+ns中的大部份时间 ?
if (excluderoom != (*roomIt)->GetID()&& (*roomIt)->GetStatus() == Room::ROOM_STATUS_HOLD &&(*roomIt)->getRoomType() == roomtype && (*roomIt)->getMaxPlayer()> (*roomIt)->GetPlayerCount())
于是,又马上改了一个新的版本
if((*roomIt)->MatchRoom(roomtype, excluderoom))
以下为测试日志:
<TIME_TRACEname='RoomMgr::MatchRoom_fun' Span='0.000075'/>
<TIME_TRACEname='RoomMgr::MatchRoom_array' Span='0.000086'/>
RoomMgr::MatchRoom_fun 使用的是STD::VECTOR,RoomMgr::MatchRoom_array 使用的是数组。无论那个版本都比老版本快了近4倍。
而且STD::VECTOR只比数组版慢了9ns, 因此STD::VECTOR对一般的应用来说,足够的快了!
小结:
在一个非常繁忙的函数中减少函数调次数会给你节省一些时间。
就本次代码重构而言,根本算不上优化,而只是不进行劣化。有很多很牛的匹配算法,但我不想让这代码变得更复杂,更因为这个算法足够快了。
一个函数的执行花了0.001s,我并不认为这会有什么性能问题,如果这个函数不是非常频繁地调用。
80%的时间花在执行20%的代码里面,严格测试,找出关键的性能瓶颈,再优化,这才是王道。
STL这玩意,很快,至少在LIUNX下 vector 如此。