读书笔记——C++高性能编程(四、五)

第四章.内存架构和性能

电脑的内存结构按照距离处理单元远近分为:stock-buffer、L1-L3缓存、主存、磁盘。其中stock-buffer到L2为每个CPU的独享资源,L3一般被设计为CPU间共享。L1的规格一般为32k,读取单位cache-line为64字节(Linux系统中有一个专门的路径可以查看,/sys/devices/system/cpu/cpu0/cache/index0),L2和L3的大小一般是以MB为单位的。

现代CPU的频率能够达到3GHz-4GHz(甚至目前已经有能达到5GHz以上的),单周期时间约为0.3ns。DDR4内存的时钟频率为400MHz,访问内存需要考虑“列访问选通延迟(column access strobe latency,CAS延迟,CL)",每次访问大约需要9.4ns。因此CPU的速度要远快于主存,但是L1的速度能够跟上CPU的频率。

如何优化内存呢?首先,了解内存的特性。访问近期访问过的32K(L1缓存大小)的速度是很快的,在访问32K之外的内存则需要从下级的缓存甚至是主存中进行读取,这就会很慢。第二点,由于缓存的加载会有预缓存的特性,因此,访问连续的内存速度要远快于随机访问。所以,我们的方法论就是使用更好的数据结构,比如块链表,兼具块访问的高效性以及链表插入的高效性。

本章最后讲解了Spectre攻击的工作原理。Spectre攻击利用的原理就是CPU流水线的分支预判。在判断失败的时候CPU虽然会冲刷流水线,但是缓存中的预加载还在,因此在访问缓存中预加载的索引,测量其速度就可以知道对应的被访问值的确切数值。

第五章.线程、内存和并发

本章对线程的访问限制进行了分析。首先讲解了对称多线程(symmetric multi-threading,SMT)。对称多线程就是说,在一个CPU上运行的线程,如果最初运行的CPU正在忙碌,但是有其他空闲的CPU,则系统可以使用空闲的CPU对这个线程进行执行。此处摘录:”实际上,在一个CPU上运行的所有线程都在争夺共享的内部资源,如寄存器。如果每个线程没有充足利用这些共享资源,则使用SMT可以显著提升性能。换句话说,SMT可以通过运行多个这样的线程来补偿一个线程的低效“。对于缓存而言,如果多个线程没有使用到L3级缓存,那么多个线程可以和平的共处,因为不存在寄存器的竞争(虽然依然存在L1级缓存的竞争),但是如果使用到L3级缓存,由于L3级缓存是共享的,在线程太多的情况下就会发现L3级的缓存受到带宽限制,读取速度会变慢,从而拉低了所有线程的速度。

并发顺序小节,主要讲解了内存对CPU的可见性以及并发顺序。缓存间的同步使用MESI协议(具体详见:【cache篇】MESI协议 - 知乎)。同步的顺序不能保证,有可能在代码里面写的是先对A进行了赋值,然后再赋值B,在另外一个CPU中看到的是B先被赋值了,然后才赋值A。由此引入了一个C++11的内存模型,该内存模型中有6中内存顺序。具体的内容和实现方法后期再补充吧,之前看过,不过没有记录下来。大概的原理就是在stock-buffer之中对内存的访问增加标志,然后在特定事件发生的时候才讲数据写回主存进行同步。

 

 

 

 

你可能感兴趣的:(开发语言,c++)