Performance 如何更好地理解现代缓存对算法性能的影响?

Performance 如何更好地理解现代缓存对算法性能的影响?,performance,caching,Performance,Caching,我在读下面的文章:他们在摘要中说: 主内存容量已经增长到大多数数据库 装进柱塞。对于主存数据库系统,索引结构 性能是一个关键瓶颈。传统内存数据 像平衡二叉搜索树这样的结构在网络上是无效的 现代硬件,因为它们不能最佳地利用CPU缓存。 哈希表,也经常用于主存索引,速度很快,但是 仅支持点查询 如何更好地理解CPU缓存的这种利用,以及它如何影响特定数据结构/算法的性能 仅仅从某个地方开始就很好,因为这种分析对我来说非常不透明,我不知道从哪里开始理解。这将是一个非常基本的答案,否则它将非常广泛。我也不

我在读下面的文章:他们在摘要中说:

主内存容量已经增长到大多数数据库 装进柱塞。对于主存数据库系统,索引结构 性能是一个关键瓶颈。传统内存数据 像平衡二叉搜索树这样的结构在网络上是无效的 现代硬件,因为它们不能最佳地利用CPU缓存。 哈希表,也经常用于主存索引,速度很快,但是 仅支持点查询

如何更好地理解CPU缓存的这种利用,以及它如何影响特定数据结构/算法的性能


仅仅从某个地方开始就很好,因为这种分析对我来说非常不透明,我不知道从哪里开始理解。

这将是一个非常基本的答案,否则它将非常广泛。我也不是这方面的专家(收集一些零碎的东西来帮助理解如何更好地优化我的热点)。但它可能会帮助你开始研究这个问题

这个话题让我想起了我的大学时代,那时计算机架构 课程只教授寄存器、DRAM和磁盘,而对其进行修饰 通过中间的CPU缓存。CPU缓存是最常见的缓存之一 这些天业绩的主要因素。

计算机内存分为一个层次结构,从绝对最大但最慢(磁盘)到绝对最小但最快(寄存器)

磁盘下面是DRAM,它仍然非常慢。上面的寄存器是CPU缓存,速度非常快(尤其是最小的一级缓存)

访问一个节点

现在让我们假设您请求从某个数据结构以某种形式访问内存,比如一个链接结构,如树或链接列表,我们只访问一个节点

注意,为了简单起见,我颠倒了内存访问的观点。通常,它从一条指令开始,将某个内容加载到寄存器中,并使进程前后运行,而不仅仅是向前运行。

虚拟到物理(DRAM)

在这种情况下,除非内存已经映射到物理内存,否则操作系统必须将页面从虚拟内存映射到DRAM中的物理地址(这非常慢,尤其是在页面错误涉及磁盘访问的最坏情况下)。这通常是在相当大的块中完成的(机器一把抓住内存),比如对齐的4KB块。因此,我们最终只为这一个节点获取了一大块旧的4KB对齐内存

DRAM到CPU缓存

现在这个4千字节的页面已经物理映射,我们仍然希望对节点做一些事情(大多数指令必须在寄存器级别操作),这样计算机就可以在CPU缓存层次结构中向下移动它(这相当慢)。通常,所有级别的CPU缓存都具有相同的缓存线大小,就像Intel上的64字节缓存线一样

要将内存从DRAM移动到这些CPU缓存中,我们必须从DRAM中获取一块缓存线大小和对齐的内存,并将其移动到CPU缓存中。我们可能还必须在途中逐出CPU缓存层次结构的各个级别中已经存在的一些数据,例如最近使用最少的内存。现在我们为这个节点获取一把64字节对齐的内存

也许在这一点上,缓存线内存可能是这样的。假设相关的节点数据是
42
,而
中的内容是围绕它的无关内存,它不是链接数据结构的一部分

要注册的CPU缓存

现在,我们将内存从CPU缓存移动到寄存器中(这种情况发生得非常快)。在这里,我们仍然在抓取少量的内存,但是一个非常小的内存。例如,我们可以获取一个64位对齐的内存块,并将其移动到通用寄存器中。因此,我们在这里获取“42”附近的内存,并将其移动到寄存器中

最后,我们对寄存器执行一些操作并存储结果,结果通常以某种方式备份内存层次结构

访问另一个节点

当我们访问链接结构中的下一个节点时,我们最终可能不得不再次执行此操作,只是为了读取一个小节点的数据。缓存线的内容可能如下所示(感兴趣的节点数据是
22

我们可以看到,硬件和操作系统可能浪费了多少精力,将大的、对齐的数据块从较慢的内存移动到较快的内存,只是为了在逐出之前访问其中的一点点

这就是为什么所有单独分配的小对象,比如不能连续表示用户定义类型的链接节点或语言,都不是非常缓存或页面友好的。当我们遍历它们、访问它们的数据时,它们往往会调用大量页面错误和缓存未命中。也就是说,除非他们得到内存分配器的帮助,内存分配器以更连续的方式分配这些节点(在这种情况下,数据或两个或多个节点可能紧挨着彼此并一起访问)

连续性和空间位置性

最有利于缓存的数据结构往往基于连续数组(它不必是一个巨大的数组,但可能是链接在一起的数组,例如展开列表)。当我们迭代一个数组并访问第一个元素时,我们可能必须执行上面描述的动作,但是一旦内存移动到缓存线中,我们就可以得到这个动作:

现在我们可以遍历数组并访问所有元素,而它是机器上第二快的内存形式