请注意,本文不是讲解处理器缓存,如果你对cpu cache这个概念不清楚,请先Google一下。
另外,本文主要针对像 C,C++ 这种产生机器码的语言的,对于像 Java,.Net 这样的字节码语言,这里所说的可能无效,至少我没研究过。
首先说说我所说的这些旧有的优化技巧从哪里来的。
原因很简单,如果你像我一样,多年只用 J2ME,或者 Flash 这样的技术开发,你是不太可能会关心处理器缓存的,而是用一些其它的性能技巧,这些技巧遇到处理器缓存问题,就失效了。
再如果你的CPU,汇编,优化知识像我一样仍停留在 80386 时代,你我掌握的优化技巧断然也是过时的。
失效技巧一,使用预先计算好的变量或者查找表
现在来怎么用查找表来计算一个32位整数里位为1的个数。
static const unsigned char BitsSetTable256[256] =
{
// 预先计算好的256个8位数的1的个数
};
int calculateBitsCount(unsigned int n)
{
unsigned char * p = (unsigned char *)&n;
return BitsSetTable256[p[0]] +
BitsSetTable256[p[1]] +
BitsSetTable256[p[2]] +
BitsSetTable256[p[3]];
}
很酷,是吧,只用了四次加法运算,我们可以想当然地认为这个算法比那些充满乘除法甚至循环的算法快。
但当有了CPU的数据缓存,情况不一样了。当 calculateBitsCount 第一次取 BitsSetTable256
数据,很有可能导致数据缓存清空重新加载 BitsSetTable256
位置的内存,会导致浪费上百指令周期,而这上百指令周期,足够用普通方法计算位数了。
比如下面这个算法,来自
http://graphics.stanford.edu/~seander/bithacks.html
unsigned int v; // count the number of bits set in v
unsigned int c; // c accumulates the total bits set in v
for (c = 0; v; c++)
{
v &= v - 1; // clear the least significant bit set
}
这个算法看似比上面查找表算法多了很多指令,还有循环,但要记住指令成本比数据成本低非常非常多(指令数量很多超出指令缓存的除外),值票价!确实值票价,因为我用这个算法替代查找表以后,确实快了。
失效技巧二,用局部变量来缓存所操作对象的成员变量
请注意,这个技巧在大多数情况下是有效的,这里只是说明某些情况下会失效。
比如有这样一个函数,
void func(SomeObject * obj)
{
int i, k, p;
int count = obj->getCount();
for(i = 0; i < 100; ++i) {
for(int k = 0; k < 100; ++k) {
for(int p = 0; p < count; ++p) {
// 处理 obj 的数据
}
}
}
}
假设 getCount 只是取一个数值。
这看起来很好,很完美,但仔细看却有一个问题。假如所有局部变量都能被放在寄存器,没有问题。但如果 count 不能被放到寄存器里呢?那么每次循环
count 都要从堆栈内存里读取,但同时又要处理 obj 的数据,这两部分极有可能不在一个数据缓存里,这就会导致频繁的数据缓存交换,慢!
如果抛弃 count,而把最内层循环改成
for(int p = 0; p < obj->getCount(); ++p) {
// 处理 obj 的数据
}
因为读取的数据都在 obj 范围内,如果都在数据缓存范围里,那就会相当快。
失效技巧三,在一个循环里干所有事
我们可能老觉得循环是慢的,因为还要跳转,所以我们宁愿在一个循环里把所有事都做了。
ObjectA * objA;
ObjectB * objB;
for(int i = 0; i < 100; ++i) {
// 对 objA 做点事
// 对 objA 做点别的事
// 对 objB 做点事
}
这有两个问题:
1,一旦循环体里的代码长度超过指令缓存,那么每次循环都要导致指令缓存动荡,无论 CPU 有几级缓存,L1 被清空重新装载,总归比直接命中 L1 缓存慢。
2,更麻烦的事,循环里在两个数据块操作,除非两个对象恰好分配的很近,否则必然导致数据缓存的动荡,慢。
如果把循环切分,
ObjectA * objA;
ObjectB * objB;
for(int i = 0; i < 100; ++i) {
// 对 objA 做点事
}
for(int i = 0; i < 100; ++i) {
// 对 objA 做点别的事
}
for(int i = 0; i < 100; ++i) {
// 对 objB 做点事
}
则指令缓存和数据缓存都会觉得很高兴,自然也就工作快一点了。
总结:
虽然上面这些技巧会失效,并不意味这些技巧是错的,很多情况下也可能真的有效。而且处理器缓存这东西优化起来不定因素很大,并无不变之规,所以具体做时还要仔细测试,方能知道哪种方法好。
分享到:
相关推荐
06-Redis缓存设计与性能优化最佳实践_ev.rar06-Redis缓存设计与性能优化最佳实践_ev.rar06-Redis缓存设计与性能优化最佳实践_ev.rar06-Redis缓存设计与性能优化最佳实践_ev.rar06-Redis缓存设计与性能优化最佳实践_...
本文深入探讨了Redis缓存的关键策略与性能优化技巧。首先,解决缓存穿透,强调缓存空对象与布隆过滤器的应用。接着,针对缓存击穿与雪崩问题,提出过期时间随机化和高可用架构设计的解决方案。特别强调热点Key重建...
05-一线大厂Redis高并发缓存架构实战与性能优化_ev.rar05-一线大厂Redis高并发缓存架构实战与性能优化_ev.rar05-一线大厂Redis高并发缓存架构实战与性能优化_ev.rar05-一线大厂Redis高并发缓存架构实战与性能优化_ev...
Hibernate缓存,性能优化
E5及超大三级缓存处理器,无法度量内存性能,winsat补丁,解决系统不能公级评分。E5超大三级缓存处理器,无法度量内存性能,winsat补丁,解决系统不能公级评分
缓存与CPU 解析处理器缓存的作用.pdf
且缓存占据了芯片的大部分面积,使得片上缓存所消耗的能量成为存储器子系统中功率损耗的主要贡献者,因此对片上缓存进行优化是提高存储器系统效率的主要途径,增强了片上多核处理器的运算性能。针对共享缓存的管理、...
CPU缓存失效导致性能下降:主板BIOS损坏导致CPU缓存失效.pdf
ListView性能优化之视图缓存
多核处理器缓存结构技术综述.pdf
ASP.NET性能优化之局部缓存
面向大数据应用的众核处理器缓存结构设计.pdf
基于数据预取的多核处理器末级缓存优化方法.pdf
上面场景中的1,如果整个页面的缓存失效都定为10分钟,则势必增加性能开销,所以最好的策略是页面的不同部分采用不同的缓存失效时长。对于场景2也一样,我们不应该为了迁就某个BANNER不能应用缓存,就让整个页面都不...
此文档对数据库缓存做了比较好的诠释,对大数据存储性能的提高有很好的解决,希望大家学习愉快。
里面分java通用篇,j2EE篇,GUI篇,EJB篇,缓存的优化等,并且提供了参考的文献
多核处理器中改进的动态缓存优化技术.pdf
地图缓存的实现和优化地图缓存的实现和优化地图缓存的实现和优化地图缓存的实现和优化
常用的后端性能优化六种方式:缓存化+服务化+异步化等
ORACLE缓存性能调整与优化研究.pdf