数据局部性

来源:互联网 发布:王者荣耀1元60点卷淘宝 编辑:程序博客网 时间:2024/06/04 19:48

现代CPU有缓存来加速内存读取,它可以更快的读取最近访问过的内存的眦邻内存,通过合理组织数据,提高内存局部性,保证数据以处理顺序排列在连续内存上,充分使用CPU缓存来加速内存读取。


数据局部性的第一准则是在遇到性能问题时使用,优化会使使代码更加复杂,更加缺乏灵活性。使用本模式时要确认你的性能问题确实是由缓存不命中引发的,通过工具来找到糟糕的缓存使用。现代软件通过抽象来解耦代码块,抽象几乎总是意味着接口,在c++中使用接口就是通过指针或者引用访问对象,意味着在内存中跳跃,造成缓存不命中。数据局部性模式就是在抽象与性能之间进行一个权衡,没有两者兼顾的完美的解决方案。


连续数组:


通过上图可以观察出,数组存储的是指针,为了获取元素就要转换指针,这就会造成缓存不命中。令人害怕的是,我们不知道这些对象是如何在内存中布局的,只能完全任由内存管理器摆布,随着实体的分配和释放,堆会组织更加乱。所以我们使用数组将AI,Physic和Render在内存中直线摆放,然后再进行遍历,在性能攸关的地方避免在内存中跳来跳去,从而提高性能。



打包数组:

假设我们做粒子系统,将所有粒子放在连续数组中,通常我们会使用一个标识来追踪其是否在使用状态,代码如下:

for (int i = 0; i < numParticles_; i++){  if (particles_[i].isActive())  {    particles_[i].update();  }}
不难发现,当我们检查粒子的这个标志位时,会将粒子的其他部分数据也加载到缓存中,如果该粒子没有在使用,跳过它检查下一个,那么该粒子加载到内存中的其他数据就浪费了。活跃的粒子越少,在内存中跳过的部分就越多,活跃粒子有效更新之间发生的缓存不命中就越多。

我们可以不监测活跃与否的标签,而是根据标签排序粒子,将所有的活跃粒子放在列表的前头,避免缓存不命中,使循环更美丽。

for (int i = 0; i < numActive_; i++){  particles[i].update();}
当一个粒子激活时,我们只需让它与第一个不活跃粒子交换位置。反激活粒子时,只需要与最后一个激活粒子交换位置。这样做的坏处就是Particle类不再知道自己的索引,不再控制其激活状态,任何想要激活粒子的代码都需要接触到粒子系统,增加耦合度。


冷/热分割:

冷/热分割就是在第一部分保存"热"数据,那么经常需要使用的数据以及指向冷组件的指针,冷组件存储使用次数较少的数据。至于哪些是热数据,哪些是冷数据,这就是很模棱两可,需要具体情况具体分析。



多态意味着即使不知道对象的类型,也能引入行为,但动态调用总会带来一些消耗。使用指针数组指向基类或接口类型,完全开放,但是对缓存不友好。这个时候可以考虑每个数组治保会同类对象,使用常规的非虚调用,管理每种类型分别的数组确实是件苦差事,而且无法解耦类型集合,但这样对缓存友好。


0 0
原创粉丝点击