之前写了一篇关于c/c++优化的一点建议,被各种拍砖和吐槽,有赞成的有反对的,还有中立的,网友对那篇博客的的评论和吐槽,我一个都没有删掉,包括一些具有攻击性的言论。笔者有幸阅读过IBM某个项目的框架代码,和我以前看过的一些代码(包括国内某顶级电信商的代码),感觉人家在细节上做的比较细,对代码的效率和安全性花了不少心思。当然国内公司也有好的代码,但是我觉得中国和美国不仅在硬件方面落后,软件方面也要落后(额,可能有人不同意我的观点。。)。这篇关于C/C++ cache优化的一点建议,个人理解能力的确有限,希望能有牛人补充一些内容,大家多多交流。同时这篇文章也是对上篇博文的一些补充性说明。后续还会有其他的关于性能调优的内容出来,欢迎大家吐槽和拍砖。关于代码要不要优化,怎么优化,优化过了,会不会影响可移植性,大家都各有说辞。也希望给出自己的观点

内容提要:

一:为什么需要memory optimization

在过去的20年cpu的速率每年提高60%左右,但是memoryspeed 每年提高10%左右。因此,人们用cache memory 弥补他们之间的速度gap,cache的使用不当可能会导致lower performance,同时你有没有思考过:SIMD指令为什么能比普通指令快上2~8倍,我们的硬件按照摩尔定律不断的提升性能,主流编译器能不能跟上这个脚步呢??

这一段简单介绍一下cache的基础知识:cache一般由两部分构成data cache和code cache(要说明一下code cache是给instruction用的),cache 还有一个level的概念,后面将介绍。为了快速访问memory,cache引入了cache line的机制,它是指把cache分成许多个32~64bytes的line单元,还有direct-mapped和N-way set-associative,这里不再详细介绍。图1给出了一些主流设备的Cache值

 

L1 cache (I/D)

L2 cache

PS2

16K/8K† 2-way

N/A

GameCube

32K/32K‡ 8-way

256K 2-way unified

XBOX

16K/16K 4-way

128K 8-way unified

PC

~32-64K

~128-512K

图1 cache memory

二:memory结构

结构如下图2所示,各个层的访问速度是有差别的cpu是1 cycle左右,L1 cache 1-5个cycle左右,L2 Cache 5-20个左右,而memory在40-100左右。

图2 memory hierarchy

关于cache优化问题,很多人嗤之以鼻,他们认为cache太小了,以至于cache missing是不可避免的。其实通过Rearrange、Reduce和Resue这三个被有些公司称为3R 准则,是可以避免一部分cache missing ,一定程度上提高访问速度。当然有些人反问:提高了又怎么样,能有明显提高吗,应该更关注功能而不是效率,不应该在一些细枝末叶上下功夫,而且你过分的优化,会导致可移植性变差,人力成本上升?好吧,你可以持有这种观点,但是我仍然希望你能把这篇文章读完。

三:针对code和data的cache优化

这里给出几个建议,第一 reorder你的函数,我指的是尽量使函数彼此引用变得紧凑,额,如果你能reorder 在linking期的object files更好了,另外如果能理解gcc的__attribute__编译属性就更好了。另外,一些大的encapsulation也会导致cache missing问题。额,关于encapsulation,很多人站出来喊:老子用C++或者其它面向对象的语言就是因为看好encapsulation所带来的安全性和易用性,当然这个观点我不反对,这里就是说明一下可能会导致cache missing问题;第二 内联、递归和大的宏可能会导致code cache missing问题,额,希望不要理解成不要用内联、宏和递归。第三:prefetching的时间要把握好,还有preloading,这两个貌似很难,具体的详细内容可以看这两篇文章CPU cache prefetching: Timing evaluation of hardware implementations,和An Effcient Architecture for LoopBased Data Preloading,这里提到这两个概念,是为了后面例子铺垫的。

例一:

// Loop through and process all 4n elements

for (int i = 0; i < 4 * n; i++)

Process(elem[i]);

const int kLookAhead = 4; // Some elementsahead

for (int i = 0; i < 4 * n; i += 4) {

Prefetch(elem[i + kLookAhead]);

Process(elem[i + 0]);

Process(elem[i + 1]);

Process(elem[i + 2]);

Process(elem[i + 3]);

}

Elem a = elem[0];

for (int i = 0; i < 4 * n; i += 4) {

Elem e = elem[i + 4]; // Cache miss, non-blocking

Elem b = elem[i + 1]; // Cache hit

Elem c = elem[i + 2]; // Cache hit

Elem d = elem[i + 3]; // Cache hit

Process(a);

Process(b);

Process(c);

Process(d);

a= e;

}

这里三段程序功能上是一样的,实现过程是有区别的,读者可以思考一下他们之间的差异性会,要考虑到cache读取数据时,是按照line读入的,函数调用时尽量能读入同一个line中的数据。下面再举一个例子说明(该例子来自Rogue WaveSoftware)例一留给读者思考。

大家看到这两个代码唯一不同的就是DATA结构体的大小,那么这对运算速度有何影响呢,实际测试program B的速度大概为program A的速度二倍左右,这只是个估计值,具体到不同的平台可能会有所差别。下面分析一下,为什么会有这种差别呢?下面看看这两个不同的DATA在cache中的情况:

上两个图可以明显看出:programB 程序一个cache line能容纳更多的a,b这个减小了cache 交换数据的次数,也节省了时间。说的专业点就是program B的fetch利用率比program A的要高,关于fetch 利用率的问题可以参考PierceJ, Mudge T. Wrong-path instructioning。读者可以考虑下面这两段代码在效率上有何不同,

第四:注意你的编译器padding机制,大家在你机器上测试一下这3个结构体的大小。你封装的数据最好紧凑些,这对fetch的效率有帮助。前面已经举过例子。

struct X {

int8 a;

int64 b;

int8 c;

int16 d;

int64 e;

floatf;

};

struct Y {

int8 a,pad_a[7];

int64 b;

int8 c,pad_c[1];

int16 d, pad_d[2];

int64 e;

float f, pad_f[1];

};

struct Z {

int64 b;

int64 e;

float f;

int16 d;

int8 a;

int8 c;

};

第五:关于使用树的问题,很明显不同的遍历方式和存储方式会对效率产生影响,也有很多算法对树的访问以及结构做了调整,当然这都是针对具体问题而言的,例子希望大家补充。Breadth-first order和Depth-first order很显然这两种访问方式会有不同的效果,也会有不同的fetch 效率。可以参看Ren J,Pan W, Zheng Y, et al. Array based HV/VH tree: an effective data structure for layout representation.无论怎样设计和访问树,尽量使你要用到的数据线性排列(可以理解成要使用的数据相互挨着),这样做的好处前面已经提到。有些程序员使用内存池去减轻一些大数据所带来的频繁内存申请和释放操作所带来的负荷和内存碎片,额,写到树想起来的,有点跑题了。。关于内存池的使用务必要小心谨慎,跟具体的程序有关。

四:Aliasing问题

所谓的Aliasing(更详细的可以参考Understanding StrictAliasing by Mike Acton)是指对同一storage location出现多次引用(如int n; int *p1 = &n; int *p2 = &n;),这的确可以影响cache性能,ANSI c/c++有这段话:Each area of memory can only beassociated with one type during its lifetime and Aliasing may only occurbetween references of the same compatible type(还有人提出这种问题也会导致编译器优化不足,,具体我也解释不清,欢迎补充)额,跑题了。当Aliasing出现时,cache区分不了多次引用来同一个storage location,它会根据变量名去prefetch data。会导致来自同一个storage location的内容被多次读入,而且可能会在不同的line中。有些编译器引入了restrict 型指针和引用来规避这种问题,这个关键词1999的ANSI/IOS已经引入了。但我不知道你的编译器是否支持。更有大牛提出了SIMD+restrict这种搭配方案可以参(http://scc.ustc.edu.cn/zlsc/sugon/intel/compiler_c/main_cls/optaps/common/optaps_vec_simd.htm),还有有的人提出尽量使用局部变量代替全局变量,尽可能的让这些引用同一stroagelocation的变量紧凑些。

五:常用的优化工具,现在主流的优化和调试工具主要来自像IBM,INTEL,GOOGLE等公司,当然还有专门做此类软件的公司,我用过的有boundcheck,IBM Rational ,FileMon,TrueCoverage,Logiscope,KlocWork 7等,都有各自的特点。希望能帮你定位的程序的问题,当然这些工具不限于优化级别,还可以帮你查错。

c/c++性能优化--- cache优化的一点杂谈的更多相关文章

  1. 高性能Linux服务器 第10章 基于Linux服务器的性能分析与优化

    高性能Linux服务器 第10章    基于Linux服务器的性能分析与优化 作为一名Linux系统管理员,最主要的工作是优化系统配置,使应用在系统上以最优的状态运行.但硬件问题.软件问题.网络环境等 ...

  2. Linux转发性能评估与优化-转发瓶颈分析与解决方式(补遗)

    补遗 关于网络接收的软中断负载均衡,已经有了成熟的方案,可是该方案并不特别适合数据包转发,它对server的小包处理非常好.这就是RPS.我针对RPS做了一个patch.提升了其转发效率. 下面是我转 ...

  3. Linux转发性能评估与优化(转发瓶颈分析与解决方式)

    线速问题 非常多人对这个线速概念存在误解. 觉得所谓线速能力就是路由器/交换机就像一根网线一样. 而这,是不可能的.应该考虑到的一个概念就是延迟. 数据包进入路由器或者交换机,存在一个核心延迟操作,这 ...

  4. Library Cache优化与SQL游标

    Library Cache主要用于存放SQL游标,而SQL游标最大化共享是Library Cache优化的重要途径,可以使SQL运行开销最低.性能最优. 1 SQL语句与父游标及子游标 在PL/SQL ...

  5. Linux服务器性能评估与优化(一)

    网络内容总结(感谢原创) 1.前言简介 一.影响Linux服务器性能的因素   1. 操作系统级         性能调优是找出系统瓶颈并消除这些瓶颈的过程. 很多系统管理员认为性能调优仅仅是调整一下 ...

  6. oracle数据迁移之Exp和Expdp导出数据的性能对比与优化

    https://wangbinbin0326.github.io/2017/03/31/oracle%E6%95%B0%E6%8D%AE%E8%BF%81%E7%A7%BB%E4%B9%8BExp%E ...

  7. MYSQL索引结构原理、性能分析与优化

    [转]MYSQL索引结构原理.性能分析与优化 第一部分:基础知识 索引 官方介绍索引是帮助MySQL高效获取数据的数据结构.笔者理解索引相当于一本书的目录,通过目录就知道要的资料在哪里, 不用一页一页 ...

  8. 1.linux服务器的性能分析与优化

    [教程主题]:1.linux服务器的性能分析与优化 [课程录制]: 创E [主要内容] [1]影响Linux服务器性能的因素 操作系统级 CPU 目前大部分CPU在同一时间只能运行一个线程,超线程的处 ...

  9. 由浅入深探究mysql索引结构原理、性能分析与优化 转

    第一部分:基础知识 第二部分:MYISAM和INNODB索引结构 1. 简单介绍B-tree B+ tree树 2. MyisAM索引结构 3. Annode索引结构 4. MyisAM索引与Inno ...

随机推荐

  1. Extjs实现进度条

    做Extjs开发中,往往后台程序可能要执行一段时间才能得到返回结果,加入进度条可以提高客户体验度,以下为两种便捷的方式: 1.提交数据前用Ext.Msg.wait('提示','正在处理数据,请稍候') ...

  2. SVN强制填写日志

    在F:\Repositories\版本库名\hooks下新建pre-commit.bat 内容如下: @echo off setlocal set SVN_BINDIR="C:\Progra ...

  3. hdu 5493 Queue(线段树)

    Problem Description N people numbered to N are waiting in a bank for service. They all stand in a qu ...

  4. 使用 React 和 Flux 创建一个记事本应用

    React,来自 Facebook,是一个用来创建用户界面的非常优秀的类库.唯一的问题是 React 不会关注于你的应用如何处理数据.大多数人把 React 当做 MV* 中的 V.所以,Facebo ...

  5. Echarts动态数据显示

    自己慢慢摸索出来的,留着以后可能会用到 一.先引入jquery,再引入echarts.js 二.配置容器 三.配置路径和数据选项等 <script type="text/javascr ...

  6. PHP调用WCF小结

    新工作第三周,做了3年多的.Net,突然急转弯做PHP,漂移过弯,速度180迈 由于数据的整合,在项目中不得不使用PHP调用WCF 一头的雾水,网上相关的资料少又少,在phpChina发个帖子,还没有 ...

  7. js判断是否为ie的方法

    原文:http://blog.sina.com.cn/s/blog_7bbe4a850100v95z.html 下面第三种亲测可用 第一种: if(window.addEventListener){ ...

  8. android中列表的滑动删除仿ios滑动删除

    大家是不是觉得ios列表的滑动删除效果很酷炫?不用羡慕android也可以实现相同的效果 并且可以自定义效果,比如左滑删除,置顶,收藏,分享等等 其实就是自定义listview重写listview方法 ...

  9. 2014年1月9日 Oracle 内存与结构

    Oracle启动时为启动一个实例 主要为 实例 SVG 数据库文件 其它文件 1.Oracle:  内存 进程  其他文件 1.1 SVG内存(Cache)  1.1.1 共享池(Shared Poo ...

  10. WebConfig

    花了点时间整理了一下ASP.NET Web.config配置文件的基本使用方法.很适合新手参看,由于Web.config在使用很灵活,可以自定义一些节点.所以这里只介绍一些比较常用的节点. <? ...