目录
最近在折腾一些性能优化的事儿, 顺便记录一下
当然, 烂大街的抄来抄去最没意思了, 这里一如既往的列一些不常见的状况
分支预测
这个其实是个挺有意思的话题, 这里不细讲, 有兴趣请先自行搜索 "分支预测" 学习一下
典型示例是: 对一个已排序的数组做 if else 遍历, 明显快于一个未排序的数组
不过通常比较少遇到上述情形, 这边列一个经常能碰到的细节:
// 示例1
T func() {
if(cond) {
return a;
}
return b;
}
// 示例2
T func() {
if(cond) {
return a;
}
else {
return b;
}
}
通常情况下, 示例2 比示例1 要快 (大约 5%~10%),
因为如果不在 else 中, return b;
将作为独立语句, 不参与分支预测
dynamic_cast 与虚函数
通常情况下, 虚函数调用只比普通函数调用略微慢一点 (多了一个虚表间接寻址的步骤)
不过 dynamic_cast 就不同了, 往往需要遍历继承树, 尤其在多继承和虚继承的时候, 性能相当糟糕
某些对性能要求高的场合, 如果能够明确继承关系和父子类的逻辑关系, 就需要各种姿势绕开 dynamic_cast 了
-
姿势1
class Base { virtual ChildA *toChildA(void) {return NULL;} virtual ChildB *toChildB(void) {return NULL;} }; class ChildA : public Base { virtual ChildA *toChildA(void) { return this; } }; class ChildB : public Base { virtual ChildB *toChildB(void) { return this; } };
比较通用, 几乎没啥副作用和坑, 不过用起来不是很灵活, 适用于父类明确知道子类类型的情况,
典型场景: 网络模块中各种 Message 类型 -
姿势2:
Child *child = isChildType(base) ? static_cast
(base) : NULL; 灵活但是坑也很大, isChildType 本身就有很多学问, static_cast 更是有讲究不能随便用的,
典型情况就是多继承中不能直接使用, 有兴趣的可以去了解下 C艹 的对象内存布局, 就明白了典型: Qt 中的
qobject_cast
, 这部分实现的源码还是比较值得一看的 (需要很多额外的辅助代码)ZFObjectCast
采用的也是同样原理
间接寻址
这是个看上去简单易懂但实际上学问非常大的性能优化问题
-
表象: 多一次寻址操作
T *p = new T(); T obj; p->func(); // 实际相当于 (*p).func(); obj.func(); // 通常情况下比上一行略快
-
深层次: 内存交换, CPU 缓存命中
要理解这些, 还得先了解一些编译原理和硬件架构, 这边简单科普一下:
-
一段程序要执行, 基本上得经历以下过程: 硬盘 > 内存 > CPU 缓存 > 寄存器 > 运算单元
速度从慢到快, 但容量也从大到小, 容量级别大约为:
硬盘 (TB/GB) > 内存 (GB/MB) > CPU 缓存 (1MB~10MB) > 寄存器 (寄存器个数 * CPU字长)
-
为了性能, 上述每个过程之间基本上都有缓存
-
缓存没命中的时候, 就必需: 删除旧的 > 从更慢一级的存储介质中读取
-
此外, 硬件架构通常还有以下限制
- 不能越级读取,
例如 CPU 缓存必须从内存中读取, 如果想要硬盘中的数据, 必须先从硬盘读取到内存中,
再从内存中读取 - 读取时通常只能读取连续的一段数据, 分页读取
- 不能越级读取,
懂了上述这些, 再回去看上面的例子
T obj;
: 局部变量, 编译器很容易和函数执行代码等优化为一段连续的数据,
很容易一次性装入 CPU 缓存中new T()
或者从别的全局变量中取地址 : 对象存储的位置大多数情况下远离函数内容所在的地址,
访问时容易涉及到缓存失效, 造成换页 (通常是 CPU 缓存未命中)
当然, 即使知道了这些, 间接寻址的性能优化问题依旧是玄学, 并没有什么万用的方法,
为了灵活性牺牲性能也是常有的事, 最简单粗暴的典型是, 把连续的指针访问替换为对象引用访问:T *p = xxx; p->a(); p->b(); p->c(); // 替换为 T *p = xxx; T &ref = *p; ref.a(); ref.b(); ref.c();
-
总结
上述提到的大多还是比较少遇到的情况, "不要过早优化" 依旧是真理,
而且大多数情况下, 性能优化都是一个权衡问题, 好好遵循二八原则,
把有限的性能优化的预算放到那 20% 的重要点上,
才是最适合的方式
转载请注明来自: http://zsaber.com/blog/p/170
既然都来了, 有啥想法顺便留个言呗? (无奈小广告太多, 需审核, 见谅)