当模拟写我的朋友说他喜欢尝试写程序的足够小到适合高速缓存。这不会有任何真正的意义呢?我的理解是,缓快于头和主存储器。是否有可能指定的程序运行的高速缓存或至少载荷的变量进入缓存?我们正在编写的模拟以任何性优化收益是巨大的好处。

如果你知道任何良好的联系,解释CPU缓存,然后点给我在那个方向。

有帮助吗?

解决方案

至少有一个典型的桌面CPU,你真的不能指定太多关于高速缓存的使用。你仍然可以试试缓存写友好的代码。在代码方面,这往往意味着展开循环(只是一个明显的例子),很少有用的--它扩展码,以及一个现代化的CPU通常最小的开销的循环。你通常可以做更多的数据面上,改善地方的基准,防止伪共享(例如两个经常使用的碎片的数据,将试图使用相同的缓存的一部分,而其他部分仍未使用的).

编辑(使一些观点一点更明确的):

一个典型的CPU有一些不同的缓冲。一个现代化桌面处理器通常将有至少2和往往3级别的高速缓存。通过(至少几乎)普遍协议"1级"的高速缓冲存储器"最近"的处理因素,而数字从那里(第2级是接下来,3级之后,等等。)

在大多数情况下,(至少)1级缓被分成两半:一个指令缓和一个数据高速缓冲存储器(英特尔486几乎是唯一的例外的,我知道,与一个单一的高速缓存这两个指令和数据--但是它是如此彻底过时的,它可能不值得很多的想)。

在大多数情况下,缓冲存组织为设置的"线"。内容缓是正常的读、写和跟踪一个线一次。换句话说,如果CPU是要使用数据从任何一部分的超高速缓冲存储器线,整个缓线是从中读取下一个较低水平的储存。高速缓存这是更接近CPU一般是较小的和较小的缓线。

这基本结构导致大部分特点的高速缓存这一问题,在编写代码。尽可能的,你想要看的东西进入缓一次,做的一切都有了它,你们要,然后转移到别的东西。

这意味着你处理的数据,这通常最好要阅读一个相对较小数量的数据(小到足以在缓),当尽力处理这些数据,你可以,然后转移到下一个大块的数据。算法像的快速排序,迅速打破的大量输入在逐步更小的碎片做这或多或少是自动的,因此他们往往是相当缓友好型的,几乎不管精确的细节缓存。

这还影响如何编写代码。如果你有一个循环,如:

for i = 0 to whatever
   step1(data);
   step2(data);
   step3(data);
end for

你们一般好串起尽可能多的步骤在一起因为你可以 金额 将适合在高速缓存。你溢缓,性能/将降大幅下降。如果对上述步骤3是足够大,不适合进入缓,你通常是更好地打破的循环达成两片这样的(如果可能的话):

for i = 0 to whatever
    step1(data);
    step2(data);
end for

for i = 0 to whatever
    step3(data);
end for

展开循环是一个相当激烈的争议问题。在一方面,它 可以 导致的代码的更多CPU,减少了开销的指令的执行为的循环本身。同时,它可以(并且一般不会)增加码的大小,所以它是相对缓不友好。我自己的经验是,在综合基准,往往要做真的很小量的处理上的真正的大量数据,使你获得很多从展开循环.在更实际的码在那里你会有更多的处理对个人的数据片,你获得很多小--和四溢的缓导致严重的性能损失并不特别罕见。

数据高速缓冲存也被限制在大小。这意味着你一般想让你的数据包装作为人口密集尽可能使尽可能多的数据将适合在高速缓存。只是一个明显的例子,数据结构的链接在一起的指针需要获得相当多的条款计算的复杂性,以弥补金额的数据的高速缓存空间使用的那些指针。如果你要用一个链接的数据结构,你一般想要至少保证你连接起来比较大块的数据。

在很多情况下,然而,我们发现的招数我最初了解到,为配合的数据进入微不足道的数额内存在的微处理器已经(主要)的过时几十年来,作出了非常好的现代化处理器。意图是现在,以适应更多的数据在缓,而不是主存储器,但效果是几乎相同。在很多情况下,可以认为CPU说明作为几乎是免费的,并整体执行的速度是由于带宽的高速缓冲存储器(或者主存储器),使额外的处理来解数据从一个密集的格式作出的对你有利。尤其是这样,当你处理的足够数据,这不会有适合在高速缓存在的所有的任何较多,因此总体速度是由的带宽来的主存储器。在这种情况下,可以执行一个 很多 指令,以节省几读存储器,并且仍然出人头地。

并行处理可能加剧这一问题。在许多情况下,重写代码,允许并行处理可能导致几乎没有增加绩效,或者有时甚至一个的性能损失。如果总体速度是由的带宽从CPU存储器,具有更多的核心竞争带宽是不可能做到任何好处(以及可以做的重大损害).在这样的情况下,使用多个内核,以提高速度往往要做更多的包装数据更加紧密,并利用甚至更多的处理能力以拆数据,所以真正的速度增加是减少消耗的带宽,而额外的核心只要让失去的时间拆的数据密集的格式。

另一个基于缓存的问题,可能会出现并行编码的分担(假共享)的变量。如果两个(或更多)的核心需要编写的相同的位置存储器,高速缓存线保持这些数据可以最终被来回穿梭之间的核心,得到每一个核心存取共享数据。结果往往是代码的运行速度较慢并行比在序(即,在一个单一的核心).有一个变化中的这种所谓的"虚假共享",其上的代码不同的核心是写单独的数据, 数据用于不同的核心结束了在同一个缓线。由于高速缓存控制数据纯粹的整个线数据,数据获取洗牌之间来回的核心,无论如何,导致完全相同的问题。

其他提示

下面是一个非常好的上缓存/通过和Christer爱立信(战争I / II的神/ III名望)内存优化。这是一对夫妇岁,但它仍然是非常相关的。

一个有用的文件,会告诉你比你想知道的关于缓存 什么每个程序员都应该知道存储器 通过Ulrich Drepper. 轩尼诗 涵盖了非常彻底。克里斯特和麦克阿克顿已经编写了一堆的好东西关于这个也是。

我觉得你应该担心更多的有关数据的高速缓存,比指令cache—在我的经验,dcache错过的更加频繁,更多的痛苦,并更有用的是固定的。

<强>更新:2014年1月13日 根据这一高级芯片设计,高速缓存未命中现在是在代码的性能绝对主导地位的因素,所以我们基本上所有的方式回到80年代中期和快速的286个芯片加载,存储,整数的相对性能瓶颈的方面算术和高速缓存未命中。

甲速成班在现代硬件由崖点击@苏尔 。 。 。 。

---我们现在返回到您的定期程序---

有时一个例子是比如何做的描述更好。本着这一精神这里是我如何改变了一些代码在芯片缓存更好地使用一个特别成功的例子。这样做是前一段时间486 CPU上和后者移植到第一代奔腾CPU。对性能的影响是类似的。

示例:下标映射

下面是我用于将数据装配到芯片的高速缓存,其具有通用效用的技术的一个例子。

我有一个双浮动向量为1,250元件长,这与非常长尾巴的流行病学曲线。曲线的“有趣”的部分只有约200独特的价值,但我不希望有一个双面如果()测试,使CPU的流水线的混乱(因而长长的尾巴,它可以使用为下标最极端值的蒙特卡洛代码将吐出),并且我所需要的分支预测逻辑了十其他条件测试的代码中的“热点”的内部。

我定居在哪里使用8位整数的向量作为下标到双载体,其余缩短至256种元素的方案。这种微小的整数所有零后前128超前零具有相同的值,和128,因此除了中间256个值,它们都指出无论是在双载体中的第一或最后一个值。

此缩水存储需求至2k双打,而对于8位标1250个字节。这种缩水10,000字节到3298。由于该方案在这个内环花费90%以上的是时间,2个载体从来没有得到排挤出8K数据缓存。该方案立即一倍的性能。这段代码在计算了超过100万按揭贷款的OAS值的过程中被击中约100十亿倍。

由于曲线的尾部被很少触摸时,这是非常可能的是,只有微小的INT矢量的中间200-300元素实际上与中间160-240保持在高速缓存中,沿着加倍表示感兴趣百分比的1 / 3/8 。这是在性能上显着增加,在一个下午完成的,对我花了一年的优化程序。

我同意杰里,因为它一直是我的经验也,谓倾斜代码对指令缓存是几乎没有对数据高速缓存/ s的优化成功。这是一个原因,我认为AMD的共同缓存并不像Intel的独立的数据和指令缓存为有帮助的。 IE:你不想指令占用了高速缓存,因为它只是不是非常有帮助。这部分是因为CISC指令集的设立初衷是弥补CPU和内存速度之间的巨大差异,除了在80年代末的像差,这几乎是一直如此。

另一个最喜欢的技术我使用,以有利于数据高速缓冲存储器,和野蛮指令高速缓冲存储器,是利用了很多位整数中的结构定义,和一般的最小可能数据尺寸。屏蔽掉4位int持有一年中的月份,或9位持有一年的一天,等等,等等,都需要占用CPU使用口罩,以屏蔽掉了位正在使用的主机整数,其中缩数据,有效地提高了高速缓存和总线的大小,但需要更多的指令。虽然这种技术产生了不能在合成基准在繁忙的系统,其中使用执行,以及,代码RS和进程竞争资源,它奇妙的作品。

主要是这将作为一个占位,直到我得到的时间做到这一主题的司法,但是我想分享我认为是真正开创性的里程碑引入专门的位操作指令中的新的英特尔Hazwell微处理器。

它成为痛苦地显而易见的时候,我写了一些码在这里计算器,以反位4096位阵列,30多年之后引入电脑,微处理器就是不要把太多的关注或资源的到位,我希望会改变。特别是,我喜欢看到,对于初学者来说,bool类型成为一个实际的比特数据类型在C/C++,而不是可笑的浪费字节的目前它是。

Hazwell's new Bit Manipulation Instructions

更新:12/29/2013

我最近有机会优化环缓冲区跟踪的512个不同的资源用户的要求的系统在毫秒的粒度。有一个计时器,火灾每一毫秒其加入的总和最新片的资源请求和中减去了1,000个时间片的要求,包括资源的请求现在毫秒1 000名老。

头尾载体是正确的下一个彼此存在,除了在第一头,然后尾包裹并开始在开始阵列。在(轧)摘要片但是在一个固定的,静态分配阵列,这不是特别密切的那些,而不是分配从堆。

思考这个问题,并学习的代码一些事项引起了我的注意。

  1. 需求是即将在加入的头和摘要切片的同时,对下一个彼此相邻的行代码。

  2. 定时器发射,尾部是减去出的摘要片,结果留在摘要片,正如你所期望的

  3. 2功能在定时器发射的先进的所有指针提供服务的环。特别是....头部复盖尾,从而占据相同的存储位置 新的尾部被占领的下一512存储地点,或者包裹

  4. 用户需要更多的灵活性,在需求数管理,从512 4098,或者也许更多。我感觉到的最强大的,白痴-的证据的方式来做,这是分配这两个1 000个时间段和摘要片都在一起作为一个毗连区块的记忆如此,这将是不可能的摘要切片的结束是一个不同的长度比其他1 000个时间段。

  5. 鉴于上述,我开始不知道如果我可以获得更多的效果,而不是具有的摘要片保持在一个位置,我有它的"漫游"之间的头部和尾部的,这样它总是旁边的头添加了新的要求,以及旁边的尾巴,当时器发射和尾部的数值必须从中减去的摘要。

我正是这样做的,但后来发现几个额外的优化进程。我改变了的代码计算出的滚动摘要,以使它离开,结果在尾,而不是摘要片。为什么?因为接下来的职能是执行memcpy()移动的摘要片进入存储器刚刚被占领的尾巴。(奇怪的但是真的,尾导致的头部,直到结束的环时它裹).通过离开的结果总结尾,我没有执行memcpy(),我只是过来分配pTail到pSummary.

在一个类似的方式,新的头被占领现在已陈旧的摘要片的老的存储位置,因此,我再次就分配pSummary到pHead和零所有其价值与memset为零。

领导方式来结束的环(真的是一个鼓512轨道宽)是尾巴,但我只需要比较其针对恒定pEndOfRing指针,以检测,条件。所有其他指针可能分配的指针价值的矢量只是提前。即:我只需要一个有条件的测试为1:3的指向正确包装。

最初设计使用了字节的整数,以最大限度缓慢,然而,我能放松这种约束的满足用户的要求,以处理较高的资源计数,每个用户每毫秒-使用符号和仍然短裤 双性能, ,因为即使有3个相邻的矢量的512未签名的短裤,L1高速缓冲存储器的32千缓存数据可以很容易地保持所需的3,720字节,2/3的人,这些地点只用。只有当的尾巴,摘要,或者头部裹1 3个分开的任何明显的"步骤",在8MB L3cache.

总的运行时期内存占用这个代码下的2MB,所以它完全运行的芯片缓存,甚至在一个i7芯片有4个核心中,4个实例的这个进程可以运行,没有任何降解性能在所有和总吞吐量的上升略5的过程在运行。这是一个巨着的万能上高速缓存的使用。

大多数C / C ++编译器倾向于以优化尺寸,而不是为“速度”。也就是说,较小的代码一般比执行的,因为高速缓冲存储器影响展开代码更快。

如果我是你,我想确保我知道哪些部分代码的热点,这是我的定义

  • 紧循环不含有任何功能的电话,因为如果它要求任何功能,那么电脑将花费大部分时间在这一职能,
  • 这一账户为一个重要部分的执行时间(如>=10%),这可以确定从一个分析器。(我只是样品堆。)

如果你有这样的热点,那么就应该适合在高速缓存。我不知道该如何你告诉它来做到这一点,但我怀疑这是自动的。

scroll top