ImageVerifierCode 换一换
格式:DOCX , 页数:13 ,大小:73.30KB ,
资源ID:2375063      下载积分:3 金币
快捷下载
登录下载
邮箱/手机:
温馨提示:
快捷下载时,用户名和密码都是您填写的邮箱或者手机号,方便查询和重复下载(系统自动生成)。 如填写123,账号就是123,密码也是123。
特别说明:
请自助下载,系统不会自动发送文件的哦; 如果您已付费,想二次下载,请登录后访问:我的下载记录
支付方式: 支付宝    微信支付   
验证码:   换一换

加入VIP,免费下载
 

温馨提示:由于个人手机设置不同,如果发现不能下载,请复制以下地址【https://www.bdocx.com/down/2375063.html】到电脑端继续下载(重复下载不扣费)。

已注册用户请登录:
账号:
密码:
验证码:   换一换
  忘记密码?
三方登录: 微信登录   QQ登录  

下载须知

1: 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。
2: 试题试卷类文档,如果标题没有明确说明有答案则都视为没有答案,请知晓。
3: 文件的所有权益归上传用户所有。
4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
5. 本站仅提供交流平台,并不能对任何下载内容负责。
6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

版权提示 | 免责声明

本文(关于现代CPU程序员应当更新的知识.docx)为本站会员(b****1)主动上传,冰豆网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对上载内容本身不做任何修改或编辑。 若此文所含内容侵犯了您的版权或隐私,请立即通知冰豆网(发送邮件至service@bdocx.com或直接QQ联系客服),我们立即给予删除!

关于现代CPU程序员应当更新的知识.docx

1、关于现代CPU程序员应当更新的知识关于现代CPU,程序员应当更新的知识有人在Twitter上谈到了自己对CPU的认识:我记忆中的CPU模型还停留在上世纪80年代:一个能做算术、逻辑、移位和位操作,可以加载,并把信息存储在记忆体中的盒子。我隐约意识到了各种新发展,例如矢量指令(SIMD),新CPU还拥有了虚拟化支持(虽然不知道这在实际使用中意味着什么)。我错过了哪些很酷的发展呢?有什么是今天的CPU可以做到而去年还做不到的呢?那两年,五年或者十年之前的CPU又如何呢?我最感兴趣的事是,哪些程序员需要自己动手才能充分利用的功能(或者不得不重新设计编程环境)。我想,这不该包括超线程/SMT,但我并不

2、确定。我也对暂时CPU做不到但是未来可以做得到的事感兴趣。本文内容除非另有说明,都是指在x86和Linux环境下。历史总在重演,很多x86上的新事物,对于超级计算机、大型机和工作站来说已经是老生常谈了。现状杂记现代CPU拥有更宽的寄存器,可寻址更多内存。在上世纪80年代,你可能已经使用过8位CPU,但现在肯定已在使用64位CPU。除了能提供更多地址空间,64位模式(对于32位和64位操作通过x867浮点避免伪随机地获得80位精度)提供了更多寄存器和更一致的浮点结果。自80年代初已经被引入x86的其他非常有可能用到的功能还包括:分页/虚拟内存,pipelining和浮点运算。本文将避免讨论那些写

3、驱动程序、BIOS代码、做安全审查,才会用到的不寻常的底层功能,如APIC/x2APIC,SMM或NX位等。内存/缓存 (Memory / Caches)在所有话题中,最可能真正影日常编程工作的是内存访问。我的第一台电脑是286在,那台机器上,一次内存访问可能只需要几个时钟周期。几年前,我使用奔腾4,内存访问需要花费超过400时钟周期。处理器比内存的发展速度快得多,对于内存较慢问题的解决方法是增加缓存,如果访问模式可被预测,常用数据访问速度更快,还有预取预加载数据到缓存。几个周期与400多个相比,听起来很糟慢了100倍。但一个对64位(8字节)值块读取并操作的循环,CPU聪明到能在我需要之前就

4、预取正确的数据,在3Ghz处理器上,以约22GB/s的速度处理,我们只丢了8的性能而不是100倍。通过使用小于CPU缓存的可预测内存访问模式和数据块操作,在现代CPU缓存架构中能发挥最大优势。如果你想尽可能高效,这份文件是个很好的起点。消化了这100页PDF文件后,接下来,你会想熟悉系统的微架构和内存子系统,以及学习使用类似likwid这样的工具来分析和测验应用程序。TLBs芯片里也有小缓存来处理各种事务,除非需要全力实现微优化,你并不需要知道解码指令缓存和其他有趣的小缓存。最大的例外是TLB虚拟内存查找缓存(通过x86上4级页表结构完成)。页表在L1数据缓存,每个查询有4次,或16个周期来进

5、行一次完整的虚拟地址查询。对于所有需要被用户模式内存访问的操作来说,这是不能接受的,从而有了小而快的虚拟地址查找的缓存。因为第一级TLB缓存必须要快,被严重地限制了尺寸。如果使用4K页面,确定了在不发生TLB丢失的情况下能找到的内存数量。x86还支持2MB和1GB页面;有些应用程序会通过使用较大页面受益匪浅。如果你有一个长时间运行,且使用大量内存的应用程序,很值得研究这项技术的细节。乱序执行/序列化 (Out of Order Execution / Serialization)最近二十年,x86芯片已经能思考执行的次序(以避免因为一个停滞资源而被阻塞)。这有时会导致很奇怪的表现。x86非常严

6、格的要求单一CPU,或者外部可见的状态,像寄存器和记忆体,如果每件事都在按照顺序执行都必须及时更新。这些限制使得事情看起来像按顺序执行,在大多数情况下,你可以忽略OoO(乱序)执行的存在,除非要竭力提高性能。主要的例外是,你不仅要确保事情在外部看起来像是按顺序执行,实际上在内部也要真的按顺序。一个你可能关心的例子是,如果试图用rdtsc测量一系列指令的执行时间,rdtsc将读出隐藏的内部计数器并将结果置于edx和eax这些外部可见的寄存器。假设我们这样做:foordtscbarmov%eax,%ebxbaz其中,foo,bar和baz不去碰eax,edx或%ebx。跟着rdtsc的mov会把e

7、ax值写入内存某个位置,因为eax外部可见,CPU将保证rdtsc执行后mov才会执行,让一切看起来按顺序发生。然而,因为rdtsc,foo或bar之间没有明显的依赖关系 ,rdtsc可能在foo之前,在foo和bar之间 ,或在bar之后。甚至只要baz不以任何方式影响移mov,令也可能存在baz在rdtsc之前执行的情况。有些情况下这么做没问题,但如果rdtsc被用来衡量foo的执行时间就不妙了。为了精确地安排rdtsc和其他指令的顺序,我们需要串行化所有执行。如何准确的做到?请参考英特尔的这份文档。内存/并发 (Memory / Concurrency)上面提到的排序限制意味着相同位置的

8、加载和存储彼此间不能被重新排序,除此以外,x86加载和存储有一些其他限制。特别是,对于单一CPU,不管是否是在相同的位置,存储不会与之前的负载一起被记录。然而,负载可以与更早的存储一起被记录。例如:mov1,%espmov%ebx,%eax执行起来就像:mov%ebx,%eaxmov1,%esp但反之则不然如果你写了后者,它永远不能像你前面写那样被执行。你可能通过插入串行化指令迫使前一个实例像写起来一样来执行。但是这需要CPU序列化所有指令这会非常缓慢,因为它迫使CPU要等到所有指令完成串行化后才能执行任何操作。如果你只关心加载/存储顺序,另外还有一个 mfence指令只用于序列化加载和存储。

9、本文不打算讨论memory fence,lfence和sfence,但你可以在这里阅读更多关于它们的内容 。单核加载和存储大多是有序的,对于多核,上述限制同样适用;如果core0在观察core1,就可以看到所有的单核规则适用于core1的加载和存储。然而如果core0和core1相互作用,不能保证它们的相互作用也是有序的。例如,core0和core1通过设置为0的eax和edx开始,core0执行:mov1,_foomov_foo,%eaxmov_bar,%edx而core1执行mov1,_barmov_bar,%eaxmov_foo,%edx对于这两个核来说, eax必须是1,因为第一指令和

10、第二指令相互依赖。然而,eax有可能在两个核里都是0,因为core0的第三行可能在core1没看到任何东西时执行,反之亦然。memory barriers序列化一个核心内的存储器访问。Linus对于使用memory barriers而不是使用locking有这样一段话 :不用locking的真正代价最终不可避免。通过使用memory barriers自以为聪明的做事几乎总是错误的前奏。在所有可以发生在十多种不同架构并且有着不同的内存排序的情况下,缺失一个小小的barrier真的很难让你理清楚事实上,任何时候任何人编了一个新的锁定机制,他们总是会把它弄错。而事实证明,在现代的x86处理器上,使用

11、locking来实现并发通常比使用memory barriers代价低,所以让我们来看看锁。如果设置_foo为0,并有两个线程执行incl (_foo)10000次一个单指令同一位置递增20000次,但理论上结果可能2。搞清楚这一点是个很好的练习。我们可以用一段简单的代码试验:#include#include#defineNUM_ITERS10000#defineNUM_THREADS2intcounter=0;int*p_counter=&counter;voidasm_inc()int*p_counter=&counter;for(inti=0;iNUM_ITERS;+i)_asm_(in

12、cl(%0)nt:r(p_counter);intmain()std:threadtNUM_THREADS;for(inti=0;iNUM_THREADS;+i)ti=std:thread(asm_inc);for(inti=0;iNUM_THREADS;+i)ti.join();printf(Countervalue:%in,counter);return0;用clang+ -std=c+11 pthread在我的两台机器上编译得到的分布结果如下:不仅得到的结果在运行时变化,结果的分布在不同的机器上也是不同。我们永远没到理论上最小的2,或就此而言,任何低于10000的结果,但有可能得到100

13、00和20000之间的最终结果。尽管incl是个单独的指令,但不能保证原子性。在内部,incl是后面跟一个add后再跟一个存储的负载。在cpu0里的一个增加有可能偷偷的溜进cpu1里面的负载和存储之间执行,反之亦然。英特尔对此的解决方案是少量的指令可以加lock前缀,以保证它们的原子性。如果我们把上面代码的incl改成lock incl,输出始终是20000。为了使序列有原子性,我们可以使用xchg或cmpxchg, 它们始终被锁定为比较和交换的基元。本文不会详细描它是如何工作的,但如果你好奇可以看这篇David Dalrymple的文章。为了使存储器的交流原子性,lock相对于彼此在glob

14、al是有序的,而且加载和存储对于锁不会被重新排序相。对于内存排序严格的模型,请参考x86 TSO文档。在C或C+中:local_cpu_lock=1;/.做些重要的事.local_cpu_lock=0;编译器不知道local_cpu_lock = 0不能被放在重要的中间部分。Compiler barriers与CPU memory barriers不同。由于x86内存模型是比较严格,一些编译器的屏障在硬件层面是选择不作为,并告诉编译器不要重新排序。如果使用的语言比microcode,汇编,C或C+抽象层级高,编译器很可能没有任何类型的注释。内存/移植 (Memory / Porting)如果要把代码移植到其他架构,需要注意的是,x86也许有着今天你能遇到的任何架构里最强的内存模式。如果不仔细思考,它移植到有较弱担保的架构(PPC,ARM,或Alpha),几乎肯定得到报错。考虑Linus对这个例子的评论:CPU1CPU2-if(x=1)z=y;y=5;mb();x=1;如果我读了Alpha架构内存排序保证正确,那么至少在理论上,你真的可以得到Z = 5mb是memory barrier(内存屏障)。本文不会细讲,但如果你想知道为什么有人会建立这样一个允许这种疯狂行为发生的规范,想一想

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1