(也以PDF格式在Ulrich Drepper自己的网站上:https://www.akkadia.org/drepper/cpumemory.pdf)
#1 楼
据我所记得,Drepper的内容描述了有关内存的基本概念:CPU缓存如何工作,物理和虚拟内存是什么以及Linux内核如何处理该动物园。在某些示例中可能有过时的API引用,但这无关紧要;不会影响基本概念的相关性。因此,任何描述基本内容的书籍或文章都不能被称为过时。 “每个程序员应该了解的内存知识”绝对值得一读,但是,我认为这并不适合“每个程序员”。它更适合系统/嵌入式/内核人员。
评论
是的,我真的不明白为什么程序员应该需要了解SRAM和DRAM在模拟级别上的工作方式-在编写程序时没有太大帮助。真正需要这些知识的人,最好花些时间阅读手册,了解有关实际时序的详细信息,等等。但是对于对硬件基础知识感兴趣的人呢?可能没有用,但至少很有趣。
– Voo
2011年11月14日在18:52
如今,性能==内存性能,因此了解内存是任何高性能应用程序中最重要的事情。这使得本文对于参与其中的任何人都至关重要:游戏开发,科学计算,金融,数据库,编译器,大型数据集的处理,可视化,必须处理大量请求的任何事物...所以是的,如果您正在处理应用程序在大多数情况下,它是闲置的,就像文本编辑器一样,直到您需要快速执行一些操作(例如找到一个单词,对单词进行计数,拼写检查)之前,论文完全没有兴趣...等等...没关系。
– gnzlbg
13年8月13日在21:24
#2 楼
PDF格式的指南位于https://www.akkadia.org/drepper/cpumemory.pdf。它仍然通常是优秀的,并受到强烈推荐(对我来说,我认为是其他性能调整专家)。如果乌尔里希(或其他任何人)编写了2017年的更新将很酷,但这将是很多工作(例如重新运行基准测试)。另请参见x86标签Wiki中的其他x86性能调整和SSE / asm(和C / C ++)优化链接。 (Ulrich的文章不是x86专用的,但他的大多数(所有)基准测试都是在x86硬件上进行的。)
关于DRAM和缓存如何工作的低级硬件详细信息仍然适用。 DDR4使用与DDR1 / DDR2(读/写突发)相同的命令。 DDR3 / 4的改进不是根本的改变。 AFAIK,所有与拱无关的东西仍然普遍适用,例如到AArch64 / ARM32。
有关内存/ L3延迟对单线程带宽的影响的重要详细信息,另请参见此答案的“延迟绑定平台”部分:
bandwidth <= max_concurrency / latency
,这实际上是单线程带宽的主要瓶颈在至强等现代多核CPU上。但是四核Skylake台式机可以接近单线程最大化DRAM带宽。该链接具有有关x86上的NT商店和普通商店的一些非常好的信息。为什么Skylake在单线程内存吞吐量方面比Broadwell-E好得多?是摘要。因此,Ulrich在“ 6.5.8利用所有带宽”中关于在其他NUMA节点以及您自己的NUMA节点上使用远程内存的建议在现代硬件上适得其反,在现代硬件上,内存控制器的带宽超过了单个内核可以使用的带宽。很可能您可以想象这样一种情况:在同一个NUMA节点上运行多个需要大量内存的线程以进行低延迟线程间通信是有净收益的,但是让它们将远程内存用于对高带宽,对延迟不敏感的东西。但这很晦涩,通常只在NUMA个节点之间分配线程,并让它们使用本地内存。由于最大并发限制(请参阅下文),每核带宽对延迟很敏感(请参阅下文),但是一个插槽中的所有内核通常可以使该插槽中的内存控制器饱和超过。
(通常)不使用软件预取
改变的一件主要事情是硬件预取比Pentium 4更好,并且可以识别较大的跨步访问模式,并且一次识别多个流(例如,一个向前/向后)每4k页)。英特尔的优化手册在其Sandybridge系列微体系结构的各种缓存中描述了硬件预取器的一些详细信息。 Ivybridge和更高版本具有下一页硬件预取功能,而不是等待新页面中的高速缓存未命中以触发快速启动。我认为AMD在其优化手册中也有类似的内容。请注意,英特尔手册中也充满了旧建议,其中一些建议仅适用于P4。对于SandB,特定于Sandybridge的部分当然是准确的,但例如HSW中微熔合微胶的非层压改变,手册中没有提及。
如今,通常的建议是从旧代码中删除所有SW预取,并且仅在分析显示缓存未命中(并且不使内存带宽饱和)时才考虑将其重新放入。预取二进制搜索下一步的两面仍然会有所帮助。例如一旦决定要看哪个元素,就预取1/4和3/4元素,以便它们可以与加载/检查中间元素并行加载。
建议使用单独的预取线程(6.3.4)是我认为这完全过时了,而且只能在Pentium 4上使用。P4具有超线程(2个逻辑内核共享一个物理内核),但是跟踪缓存(和/或乱序的执行资源)不足以获取运行的吞吐量同一内核上有两个完整的计算线程。但是现代的CPU(Sandybridge-family和Ryzen)要强大得多,应该要么运行一个真实的线程,要么不使用超线程(使另一个逻辑核心保持空闲状态,以便单线程拥有完整的资源而不是对ROB进行分区)。
软件预取一直很“脆弱”:获得加速的正确魔术调整数字取决于硬件的详细信息,可能还取决于系统负载。太早了,它在需求负载之前就被逐出了。太晚了,它没有帮助。这篇博客文章显示了代码+图形,这是在Haswell上使用SW预取来预取问题的非顺序部分的有趣实验。另请参阅如何正确使用预取指令? NT预取很有趣,但是更加脆弱,因为L1的早期撤离意味着您必须一直到L3或DRAM,而不仅仅是L2。如果您需要性能的每一个下降,并且可以针对特定计算机进行调优,则值得考虑使用SW预取进行顺序访问,但是如果您有足够的ALU工作来解决内存瓶颈时,这仍然可能会减慢速度。
缓存行大小仍然是64个字节。 (L1D读/写带宽非常高,如果所有CPU都命中L1D,则现代CPU可以在每个时钟上执行2个矢量加载+ 1个矢量存储。请参阅如何快速地进行缓存?。)使用AVX512,行大小=矢量宽度,因此您可以在一条指令中加载/存储整个缓存行。因此,每个未对齐的加载/存储都越过缓存行边界,而不是跨过256b AVX1 / AVX2的其他缓存,这通常不会减慢不在L1D中的数组的循环。
未对齐的加载指令为零如果地址在运行时对齐,则代价是不小的,但是如果编译器(尤其是gcc)知道任何对齐保证,则在进行自动矢量化处理时会编写更好的代码。实际上,未对齐的操作通常很快,但是分页仍然很麻烦(不过,在Skylake上,分页操作的影响要小得多;与100相比,只有11个额外的周期延迟,但仍然会影响吞吐量)。
如Ulrich预测的那样,如今,每个多插槽系统都是NUMA:集成内存控制器是标准配置,即没有外部北桥。但是SMP不再意味着多路,因为多核CPU已普及。从Nehalem到Skylake的Intel CPU已经使用了大型的三级缓存作为内核之间一致性的支持。 AMD CPU有所不同,但我不清楚细节。
Skylake-X(AVX512)不再包含兼容的L3,但我认为仍然有一个标记目录,可用来检查芯片上任何地方缓存的内容(如果是这样的话),而实际上没有向所有核心广播监听消息。不幸的是,SKX使用的是网状而不是环形总线,其延迟通常比以前的多核Xeon还要差。
基本上,关于优化内存位置的所有建议仍然适用,只是在可以时会发生什么的细节。避免避免缓存丢失或争用。
6.4.2原子操作:基准测试显示CAS重试循环比硬件仲裁的
lock add
差4倍,这可能仍反映了最大争用情况。但是在实际的多线程程序中,同步保持在最低限度(因为它很昂贵),因此竞争很低,并且CAS重试循环通常成功而无需重试。C ++ 11
std::atomic
fetch_add
将编译为一个lock add
(如果使用返回值,则为lock xadd
),但是使用CAS执行lock
ed指令无法完成的操作的算法通常不会造成灾难。使用C ++ 11 std::atomic
或C11 stdatomic
代替gcc旧版__sync
内置插件或较新的__atomic
内置插件,除非您想将原子访问和非原子访问混合到同一位置... 8.1 DWCAS(
cmpxchg16b
):您可以哄骗gcc发出它,但是如果您只想有效负载一半的对象,就需要丑陋的union
技巧:如何用c ++ 11 CAS实现ABA计数器? (不要将DWCAS与2个单独的内存位置的DCAS混淆。DWCAS无法实现DCAS的无锁原子仿真,但是事务性内存(如x86 TSX)可以实现。)8.2.4事务性内存:经过几次错误的启动(由于很少触发的错误,此错误被释放,然后被微码更新禁用),英特尔在较晚型号的Broadwell和所有Skylake CPU中都具有可用的事务存储。该设计仍然是David Kanter为Haswell描述的。有一种锁消除方法可以使用它来加速使用(并且可以使用)常规锁(尤其是对容器的所有元素使用一个锁)的代码,因此,同一关键节中的多个线程通常不会发生冲突),或编写直接了解交易的代码。
更新:现在,英特尔已通过微代码更新在更高版本的CPU(包括Skylake)上禁用了锁定清除功能。如果操作系统允许,TSX的RTM(xbegin / xend)非透明部分仍然可以正常工作,但是TSX通常正认真地转变为查理·布朗的足球。
具有硬件锁消除功能归因于幽灵缓解? (是的,但由于是MDS类型的侧通道漏洞(TAA),而不是Spectre。我的理解是,更新的微代码确实完全禁用了HLE。在这种情况下,操作系统只能启用RTM,而不能启用HLE。)
7.5 Hugepages:匿名透明的大页面在Linux上可以很好地工作,而无需手动使用ugeltlbfs。使用2MiB对齐方式使分配> = 2MiB(例如
posix_memalign
或不强制执行愚蠢的ISO C ++ 17要求的aligned_alloc
在size % alignment != 0
时失败)。默认情况下,与2MiB对齐的匿名分配将使用大页面。
echo defer+madvise >/sys/kernel/mm/transparent_hugepage/defrag
可能会受益于某些工作负载(例如,在分配大容量内存后会继续使用一段时间),以使内核在需要时对物理内存进行碎片整理,而不是回落到4k页。 (请参阅内核文档)。在进行大量分配(最好仍然使用2MiB对齐)后使用madvise(MADV_HUGEPAGE)
,以更强烈地鼓励内核立即停止并进行碎片整理。 defrag = always
对于大多数工作负载而言过于激进,并且将花费更多的时间在页面周围进行复制,这比节省TLB丢失所花费的时间更多。 (kcompacted可能会更有效。)BTW,Intel和AMD称2M页面为“大页面”,而“巨大”仅用于1G页面。 Linux对大于标准大小的所有内容都使用“大页面”。
(32位模式旧式(非PAE)页表的下一个最大大小只有4M页,只有具有更紧凑条目的2级页表。下一个大小应该是4G,但这就是整个地址空间,翻译的“级别”是CR3控制寄存器,而不是页面目录条目。如果IDK与Linux的术语有关,则为IDK。)
附录B:Oprofile:Linux
perf
已基本取代oprofile
。 perf list
/ perf stat -e event1,event2 ...
具有用于编程硬件性能计数器的大多数有用方法的名称。perf stat -etask-clock,context-switches,cpu-migrations,page-faults,cycles,\
branches,branch-misses,instructions,uops_issued.any,\
uops_executed.thread,idq_uops_not_delivered.core -r2 ./a.out
ocperf.py包装器将事件名称转换为代码,但如今
perf
具有该功能内置。有关使用它的一些示例,请参见x86的MOV真的可以“免费”吗?为什么我根本无法复制呢?。
评论
很有启发性的答案和指示!这显然值得更多的选票!
–claf
18-2-28在13:44
@Peter Cordes您还建议阅读其他指南/论文吗?我不是一个高性能的程序员,但是我想了解更多有关它的信息,并希望能将一些可以融入我日常编程的实践。
–user3927312
19年4月9日在7:26
@ user3927312:agner.org/optimize是专门针对x86的低级内容的最佳,最一致的指南之一,但是其中一些一般性思想适用于其他ISA。除asm指南外,Agner还具有优化的C ++ PDF。有关其他性能/ CPU体系结构链接,请参见stackoverflow.com/tags/x86/info。我还写了一些有关优化C ++的方法,通过值得一看的编译器的asm输出来帮助编译器为关键循环创建更好的asm:用于测试Collatz猜想的C ++代码比手写的asm更快?
– Peter Cordes
19年4月9日在7:31
次要nitpick-2个MiB页面不是80x86上的“巨大”页面。对于80x86,页面大小为4 KiB,大(2 MiB或4 MiB)和巨大(1 GiB);并且此术语(普通/普通,大型和大型)被所有内容(Windows,Linux等)共享。 AMD还开始将“将四个物理上和线性上连续的页面合并为一个TLB条目”,创建了一种“伪16 KiB页面”大小(中等页面?我不知道)。
–布伦丹
12月15日在1:16
@PeterCordes:“大页面”是Intel和AMD一直称为2 MiB(和4 MiB)的页面。 Windows也将它们称为大页面(例如VirtualAlloc()的MEM_LARGE_PAGES标志)。 Linux似乎支持一个或另一个,但不能同时支持两者,并且在两种情况下都使用相同的词。请注意,这对于操作系统的残酷程度来说是相当令人震惊的(Windows根本不支持1 GiB页面,仅需要使用2 MiB页面就需要特殊许可,不允许2 MiB页面成为“可分页的”; Linux带有2个黑客系统分开的系统,无法选择用户空间)
–布伦丹
12月15日下午5:15
#3 楼
从我的快速浏览来看,它看起来非常准确。需要注意的一件事是“集成”和“外部”存储控制器之间差异的部分。自从i7系列产品发布以来,英特尔CPU已全部集成在一起,并且自AMD64芯片首次发布以来,AMD一直在使用集成内存控制器。自从撰写本文以来,并没有改变太多,速度也提高了,内存控制器也变得更加智能(i7会将写入RAM的时间推迟到感觉提交更改为止)。 ,但变化不大。至少不是软件开发人员会关心的任何方式。
评论
我本来想接受你们两个的。但是我赞成你的帖子。
–Framester
2011-11-15 11:23
与SW开发人员相关的最主要的更改可能是预取线程是一个坏主意。 CPU足够强大,可以通过超线程运行2个全线程,并且具有更好的硬件预取。通常,SW预取的重要性要小得多,尤其是对于顺序访问而言。看我的答案。
– Peter Cordes
17 Dec 8'在12:34
评论
有人知道我是否可以在某处下载mobi格式的本文,以便在kindle上轻松阅读?由于缩放/格式问题,很难阅读“ pdf”它不是手机,但LWN将本文作为一组文章进行了整理,便于在手机/平板电脑上阅读。首先是在lwn.net/Articles/250967