C++性能优化笔记-8-优化存储访问

优化存储访问

代码和数据缓存

缓存是主存的代理。缓存是为了以最快的可能访问最常用的数据。

缓存组织

大多数chache以行和集合的方式组织。cache机制的更多细节,参考(en.wikipedia.org/wiki/L2_cache)。
如果程序中包含很多变量和对象,它们又刚好分布在映射到相同cache的内存,就会频繁引发cache冲突。
下边的小节,讨论如何尽量减少cache冲突。

一起使用的函数应该存储在一起

函数通常以它们在源代码中出现的顺序存储。因此,收集代码中关键部分使用的函数,并放到同一个源码文件中是一个好主意。把很少使用的函数与常用函数分离;很少运行的分支(例如,错误处理)放到函数结尾或单独封装一个函数。
有时,出于模块化的原因,函数会被保存在不同源码文件。这时,通过控制模块的链接顺序来尽量达到上述目的。链接顺序通常是模块在项目管理文件或Makefile中出现的顺序。通过一个来自链接器的映射文件来检查内存中函数的顺序。映射文件给出了每个函数与程序起始地址的相对地址。映射文件包含从静态库(.lib.a)中链接的函数地址,但不包括动态库(.dll.so)的。没有简易的方法来控制动态链接库函数的地址。

一起使用的变量应该存储在一起

缓存不命中的代价很高。取cache中的变量只需要几个时钟周期;如果cache不命中,则可能需要几百个时钟周期来获取RAM中的变量。
如果一起使用的数据在内存中也存储在彼此附近,那么cache可以高效的工作。变量和对象最好在使用它们的函数中声明。这些变量和对象会被存储在栈上,类似于Level-1 cache。变量存储的细节,可以参考变量存储。如果可能,避免使用全局和static变量,避免动态内存分配。
OOP是保存数据在一起的有效方式。一个类的数据成员总是一起保存在一个类的对象中。父类和子类的数据成员都保存在一个子类的对象中。
如果你有一个大的数据结构,数据存储的顺序是重要的。例如:有两个数组,其元素以a[0],b[0],a[1],b[1],...被交替访问,那么可以通过用结构体数组方式组织数据来提高性能:
。。。

一些编译器会用不同的内存空间来存不同数组,即使它们从不同时使用。例如:
。。。
把简单变量放到union中不是优化的,因为会妨碍使用寄存器变量。

数据对齐

如果一个变量存储的地址可以被其大小整除,则变量的访问是最高效的。大小应该总是2的次幂。大于16字节的对象应该存在可以被16整除的地址。
结构体和类成员的对齐可能引发cache空间的浪费。参考例7.39
你可以选择以cache行大小来对齐大的对象和数组,通常是64字节。这确保对象数组与cache行开头相符。一些编译器会自动对齐大的static数组,但你可能也最好显式指定对齐:alignas(64) int BigArray[1024];
后续讨论动态分配内存的对齐。

动态内存分配

对象和数组可以使用newdeletemallocfree动态分配内存。如果在编译时无法得知所需内存大小时,可以动态分配。
四种动态分配内存的典型应用:

  • 编译时不知道大小的大数组;
  • 编译时不知道数目的对象;
  • 可变大小的字符串或类似的对象;
  • 对于栈来说太大的数组;

动态分配内存的好处:

  • 一些情形中,使程序结构更清晰;
  • 按需分配,不需要按最糟情形分配固定大小的内存。提高了数据cache效率;
  • 当无法预先给出所需内存空间量的合理上限时,此功能非常有用;

动态分配内存的缺点:

  • 动态分配内存和回收比较耗时。
  • 不同大小的对象的随机顺序分配和释放,堆内存会碎片化。降低了数据缓存的效率。
  • 堆管理会在堆内存碎片化严重时运行垃圾回收,这可能发生在不合适的时机。
  • 在对象被释放后,程序员应该确保这个对象不可再被访问。
  • 被分配的内存可能没有优化对齐。
  • 对编译器来说,优化使用指针的代码是困难的,因为不可以排除别名。参考指针别名
  • 当矩阵或多维数组的行长度在编译时不可知(每次访问需要计算行地址)时,编译器可能不能使用归纳变量进行优化。

。。。
通常,为多个对象分配大块内存,比每个对象分配小块内存

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值