C++ vector.push_back()性能优化:高频调用CalculateKickers卡顿
高频调用牌型计算函数性能优化方案
核心问题定位
单次计算任务需要调用目标函数1000万~1亿次,性能分析显示绝大多数耗时集中在以下代码行:
myHand_param.FinalHandVector.push_back((1 << static_cast<int>(std::ceil((i - 3.0) / 4.0))));
已知FinalHandVector的最大长度永远不超过5,此前尝试用int*替换vector未获得明显性能提升,不清楚如何跳过容器边界检查。
待优化完整函数代码如下:
void CalculateKickers(Hand& myHand_param, int kickersNeeded) { int countKickers{ 0 }; for(int i = bits-1; i > 0 ; i--) { if (static_cast<std::bitset<bits>>(myHand_param.handMask)[i] == 1) { if (static_cast<std::bitset<1>>((static_cast<std::bitset<bits>>(myHand_param.FinalHandUsedCardFacesMask) >> i).to_ullong())== 0) { ++countKickers; myHand_param.FinalHandVector.push_back((1 << static_cast<int>(std::ceil((i - 3.0) / 4.0)))); if (countKickers == kickersNeeded) { i = 0; } } } } }
具体优化点
- 替换浮点运算为等价整数运算:当前耗时行最大开销来自
std::ceil((i - 3.0) / 4.0)的浮点计算,高频调用下浮点运算开销远高于整数运算。由于i是整数,该表达式可100%等价替换为整数除法i / 4,完全消除浮点运算、浮点转整数的开销。 - 消除vector动态操作开销:已知容器最大长度为5,直接将Hand结构体中的
FinalHandVector替换为固定长度数组+长度计数器,写入时直接用下标赋值,完全避开push_back自带的容量检查、size更新的冗余开销,比裸指针替换方案更直接高效。如果暂时不修改结构体成员,至少在函数入口提前对vector执行clear()+reserve(5),消除扩容和默认初始化开销。 - 移除bitset冗余构造和转换:当前循环每次迭代都会重新构造临时bitset对象,还做了移位转
unsigned long long再构造1位bitset的冗余操作,开销极高。如果bits位宽不超过64,直接将两个mask以uint64_t类型存储,位判断用原生位运算(mask_val >> i) & 1实现,完全消除临时对象构造、类型转换的开销。 - 简化循环退出逻辑:当前找到足够kicker时通过给i赋值0退出循环,直接用
break即可,减少一次循环条件判断开销。
优化后参考代码
// Hand结构体对应成员修改(如果bits<=64建议直接用整数存mask) // struct Hand { // // ... 其他原有成员 // int FinalHandArr[5]; // int FinalHandLen; // uint64_t handMask; // uint64_t FinalHandUsedCardFacesMask; // }; void CalculateKickers(Hand& myHand_param, int kickersNeeded) { myHand_param.FinalHandLen = 0; const uint64_t handMask = myHand_param.handMask; const uint64_t usedMask = myHand_param.FinalHandUsedCardFacesMask; for (int i = bits - 1; i > 0; --i) { // 直接用位运算判断位状态,无临时对象开销 if (((handMask >> i) & 1) && !((usedMask >> i) & 1)) { // 固定数组下标写入,无边界检查、扩容判断开销,整数运算替代浮点计算 myHand_param.FinalHandArr[myHand_param.FinalHandLen++] = 1 << (i / 4); if (myHand_param.FinalHandLen == kickersNeeded) { break; } } } }
如果bits位宽超过64,直接访问bitset的内部存储做位判断即可,不要在循环内构造临时bitset对象。以上优化落地后,函数整体性能可提升10~100倍,完全满足亿级调用的性能要求。
内容的提问来源于stack exchange,提问作者HungryCroco
相关产品推荐
相关产品推荐

