内存 论文小记


Chapter 5
内存性能及解决方式

5.2 数据存取模式问题

数据存取模式影响cache和内存相关的性能,变换数据顺序也可以改善数据分布。有些低效存取模式,以及每种都有一系列的优化方式。

5.2.1 低效循环嵌套

如果沿着错误的轴遍历该数组,则在程序继续到下一个cache line之前,将从每个cache line使用一个元素。
例如,考虑以下在二维矩阵上迭代的部分C代码:

double array[SIZE][SIZE];

for (int col = 0; col < SIZE; col++)
  for (int row = 0; row < SIZE; row++)
    array[row][col] = f(row, col);

在外循环的一次迭代中,内循环将访问特定列的每一行中的一个元素。

对于循环所触及的每个元素,都会访问新的cache line。 处理器的第一级缓存可能不包含几百个缓存行。 如果数组有几百行,那么到达矩阵底部时,可能已经从高速缓存中逐出了矩阵顶部的行。 然后,外循环的下一次迭代必须再次重新加载所有高速cache line,从而导致所触摸的每个元素都发生高速缓存未命中。 这完全破坏了循环的性能。

由于每个取回的cache line仅使用一个元素,因此错误的循环嵌套还会在取回未使用的数据时浪费大量内存带宽。

可以通过更改循环的嵌套来解决此问题,以使循环沿行而不是沿列遍历矩阵。

double array[SIZE][SIZE];

for (int row = 0; row < SIZE; row++)
  for (int col = 0; col < SIZE; col++)
    array[row][col] = f(row, col);

现在,外循环的每次迭代都使用每个高速缓存行中的所有元素,并且以后的迭代将不必重复使用相同的高速缓存行,从而大大减少了高速缓存未命中和高速缓存行的获取。

顺序访问数据还有助于硬件预取器很好地完成其工作。 这将最大程度地利用可用内存带宽。

如果在不更改计算结果的情况下无法更改循环嵌套,则可以选择对矩阵进行转置。 通过更改数据布局,访问模式将变得更加规则,从而更好地利用提取的catch line,并帮助硬件预取器做得更好。 错误的循环嵌套的代价可能远远超过转置矩阵的代价。

错误的循环嵌套并非二维矩阵独有。 当遍历任何常规多维数组(例如,在3维矩阵中)时,可能会发生相同的效果。

与C相比,Fortran沿相反的方向组织其多维数组,因此,如果在Fortran中进行编程,则需要沿着列而不是沿着行遍历矩阵。

5.2.2 随机访问模式

随机内存访问模式通常有害于缓存和内存性能。对于随机访问模式,我们并不是指真正的随机访问,而是通常不访问顺序地址,不访问相同数据或接近最近使用的数据的访问。

如第3章“高速缓存简介”中所述,高速缓存的期望是最近访问过的数据和接近最近访问过的数据的数据更有可能再次被访问。随机访问模式很糟糕,因为它们通常很少包含此类重用,这意味着在缓存中找到被访问数据的可能性很小。

随机访问模式通常还会导致低缓存行利用率。可以访问高速缓存行中的各个数据元素,而无需触摸其余的高速缓存行。与实际使用的数据量相比,这增加了应用程序的提取率和内存带宽要求。

现代处理器中存在的硬件预取器还依赖于查找常规访问模式来确定要预取的数据,从而隐藏内存访问等待时间。因此,随机访问模式使硬件预取器无效。某些类型的不规则访问模式甚至可能诱使硬件预取器预取无用的数据,从而浪费内存带宽。

当访问接近最后访问地址的地址时,即使访问主存储器也更快。

随机访问模式可以源自不同的来源:

.数据结构
.动态内存分配
.演算法

一些数据结构固有地导致随机访问模式。

例如,哈希表通常被设计为在整个表中随机分布元素,以避免冲突。因此,表中的元素查找会导致随机访问模式。

树结构是随机访问模式的另一个常见来源。在树中查找不同的键会导致遍历整个树的不同路径,从而导致看似随机的内存访问模式。

用更具缓存友好性的数据结构替换此类数据结构可以提高性能。有关更多信息,请参见第5.5节“通用数据结构”。

动态内存分配可以有助于将相关的数据对象放置得彼此远离。内存分配器通常尝试按顺序分配内存区域,但是当应用程序运行且堆上的内存被分配,重新分配并再次分配时,堆可能会变得碎片化。然后可以将已分配的内存区域分布到整个堆中。

由于应用程序进行了其他分配,因此在应用程序运行时递增分配的数据结构也很可能会散布在地址空间上。

由动态内存分配引起的随机访问模式的示例可以是从新分配的内存区域创建的链表。存储器区域可以通过地址空间或多或少地随机分布。当应用程序遍历该列表时,它将因此导致以某种或多或少的随机模式进行内存访问。如果在执行过程中将元素递增地添加到列表中,由于内存碎片,可能使问题进一步恶化。

如果由于动态内存分配而导致随机内存访问模式出现问题,则有可能实现一个将相关数据紧密放置在一起的自定义内存分配器。例如,如果您有一个在执行过程中增量分配的数据结构,则可以在应用程序启动时为该数据结构分配一个内存池,然后使用该池中的内存进行增量分配。

最后,某些算法可能会固有地导致不规则的访问模式。

许多图算法是此类算法的示例。如果将图表示为邻接矩阵,则数据结构本身非常规则。但是,如果您在图形中执行深度优先或宽度优先搜索,则该算法将导致对邻接矩阵的不规则访问,因为它遵循图形节点之间的边缘。

解决由算法引起的随机访问模式通常很困难。可能存在具有更好缓存性能的替代算法,但在其他方面可能会较差。有时可能需要使数据结构适应算法,例如,可以对邻接矩阵进行排序,以使所连接的节点彼此靠近,从而沿边缘移动会形成更规则的访问模式。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值