当前位置: 首页 > 知识库问答 >
问题:

硬件是否将多个代码操作整合到一个物理CPU操作中?

狄法
2023-03-14

我读过一篇2006年的文章,内容是关于CPU如何对整个一级缓存线进行操作,即使在您只需要对一级缓存线所包含内容的一小部分进行操作的情况下(例如,加载一整条一级缓存线以写入布尔变量显然是过分了)。本文鼓励通过以一级缓存友好的方式管理内存进行优化

假设我有两个int变量,它们恰好在内存中是连续的,在我的代码中,我连续写入这两个变量。

硬件是否将我的两个代码操作合并为单个l1行上的一个物理操作(假设CPU有一个足够大的l1缓存行来容纳两个变量),还是不?

有没有办法向C或C中的CPU建议这样的事情?

如果硬件没有以任何方式进行整合,那么您认为如果在代码中实现这样的事情,它会产生更好的性能吗?分配一个与l1行大小相同的内存块,并用尽可能多的热数据变量填充它?

共有3个答案

蓝飞
2023-03-14

缓存的全部目的是允许快速执行许多高度本地化的内存操作。

当然,最快的操作涉及寄存器。使用它们所涉及的唯一延迟是在指令获取、解码和执行过程中。在一些寄存器丰富的体系结构(以及向量处理器)中,它们实际上像专用缓存一样使用。除速度最慢的处理器外,所有处理器都有一个或多个级别的高速缓存,除了速度更快之外,这些缓存看起来就像普通指令的内存。

为了相对于实际处理器进行简化,请考虑一个假设的处理器,其运行频率为2 GHz(每个时钟0.5 ns),内存加载任意64位(8字节)内存字需要5 ns,但从内存中加载每个连续的64位字只需要1 ns。(也假设写入是相似的。)在这样的机器上,在内存中翻转一个位的速度非常慢:1 ns加载指令(仅当指令尚未在管道中时,但在远程分支后为5 ns),5 ns加载包含该位的字,0.5 ns执行指令,5 ns将更改后的字写回内存。内存拷贝更好:加载指令大约为零(因为管道可能对指令html" target="_blank">循环做了正确的事情),加载前8个字节为5 ns,执行指令为0.5 ns,存储前8个字节为5 ns,每增加8个字节为1 0.5 1 ns。地理位置使事情变得更容易。但有些操作可能是病态的:递增数组的每个字节会加载最初的5 ns、0.5 ns指令、最初的5 ns存储,然后再存储1 0.5 1个字节(而不是每个字)。(不在同一单词边界上的内存拷贝也是个坏消息。)

为了使这个处理器更快,我们可以添加一个缓存,将缓存中的数据在指令执行时间内的加载和存储提高到仅0.5 ns。内存拷贝在读取方面没有改善,因为前8字节的工作仍然需要5 ns,额外的字需要1 ns,但写入速度要快得多:缓存填满之前,每个字需要0.5 ns,填满后的速度为正常的5 1 1等,与其他使用内存较少的工作并行。对于初始加载,字节增量提高到5 ns,对于指令和写入,字节增量提高到0.5 0.5 ns,然后对于每个附加字节,字节增量提高到0.5 0.5 0.5 ns,除非在读或写缓存暂停期间。重复相同的几个地址会增加缓存命中的比例。

真正的处理器、多级缓存等会发生什么?简单的答案是事情变得更加复杂。编写缓存感知代码包括尝试改进内存访问的局部性、分析以避免冲击缓存以及大量分析。

壤驷康裕
2023-03-14

这是一个相当广泛的问题,但我将努力涵盖要点。

是的,将数据读入缓存只查看单个bool有点浪费——但是,处理器通常不知道您在此之后计划做什么,例如,如果您需要下一个连续的值与否。您可以依靠位于同一类或结构中的数据彼此相邻/靠近,因此使用它来存储您经常在彼此靠近的地方一起操作的数据会给您带来好处。

至于“一次处理多个数据”,大多数现代处理器都有各种形式的扩展,可以对多个数据项(SIMD-同一条指令,多个数据)执行相同的操作。这始于20世纪90年代末的MMX,现已扩展到包括3DNow!,x86的SSE和AVX。ARM中有“Neon”扩展,它也提供类似的功能。PowerPC也有一些类似的东西,我现在想不起它的名字。

C或C程序无法立即控制指令的选择或缓存使用。但是,如果有正确的选项,现代编译器将生成代码,例如使用SIMD指令将所有int相加在一个更大的数组中,一次添加4个项目,然后,当完成全部工作后,水平添加4个值。或者如果您有一组X、Y、Z坐标,它很可能会使用SIMD将两组此类数据添加在一起。这是编译器的选择,但这可以节省相当多的时间,因此编译器中的优化器正在被修改以找到这有帮助的情况,并使用这些类型的指令。

最后,大多数更大的现代处理器(自ca 1995年以来的x86、ARM A15、PowerPC)也执行超标量执行-一次执行多条指令,并且“无序执行”(处理器理解指令的依赖性,并执行“准备好”执行的指令,而不是完全按照给处理器的顺序)。编译器将知道这一点,并尝试“帮助”安排代码,以便处理器能够轻松完成任务。

咸疏珂
2023-03-14

高速缓存行的大小主要与并发有关。它是可以在多个处理器之间同步的最小数据块。

正如您所建议的,有必要加载整个缓存线,以便只对其中的几个字节执行操作。如果在同一个处理器上执行多个操作,尽管不需要不断重新加载。顾名思义,它实际上是缓存的。这包括缓存对数据的写入。只要只有一个处理器在访问数据,您通常就可以放心,它在高效地访问数据。

在多个处理器访问数据的情况下,对齐数据可能会有所帮助。使用C对齐属性或编译器扩展可以帮助您获得按所需方式对齐的数据结构

你可能会对我的文章《CPU重新排序》感兴趣——实际上重新排序的是什么?这给了我们一些关于低层发生的事情(至少在逻辑上)的见解。

 类似资料:
  • PFMERGE destkey sourcekey [sourcekey ...] 将多个 HyperLogLog 合并为一个 HyperLogLog ,合并后的 HyperLogLog 的基数估算值是通过对所有给定 HyperLogLog 进行并集计算得出的。 命令的复杂度为 O(N) , 其中 N 为被合并的 HyperLogLog 数量, 不过这个命令的常数复杂度比较高。

  • 在特定的活动流下,我无法将意图交付给:这是一个场景: 考虑3个活动,Home、B和C。C有两个片段CF1和CF2 B、 CF1和CF2使用相同的IntentService类,但操作不同 IntentService开始使用。(getActivity()。片段的startService(意图) 无论IntentService在哪里启动,如果它在Activity/Fragment的中运行,我都会使用来确

  • 我关心的是提高源代码可读性,并且它涉及到通过将庞大的方法分解成更小的(简洁的)方法来减小它们的大小。简单地说,我有一个非常完整的方法,它可以做很多不同的事情,例如: 我想将方法更改为: 我关心的是应用这种重构时的性能(时间和内存)。对于一个很小的人名单,当然不是问题,但是我担心的是这个名单的渐近增长。 例如,对于更老式的我可以看到以下影响: 我知道Java是如何将foreach指令转换为的。因此,

  • 今天早上,我发现了我的GitHub Actions BETA版邀请,并开始玩它,目的是迁移一些我目前在CircleCi上运行的简单构建、测试和部署管道。 我仍然在试图理解操作,但我心目中的流程是,在推动之后,工作流中的第一个操作将启动一个Docker容器。在这个容器中,我将运行一些简单的构建过程,比如最小化资产和移除人工制品。接下来的操作将在构建上运行一些测试。管道中的下一个操作将部署到许多环境中

  • 假设我们有两个微服务和,如果我们的用户想上传一个头像。请求应该由网关直接路由到还是?从cohesion的角度来看,所有其他信息都是由服务处理的,处理上传请求并将请求转发到更有意义。从性能的角度来看,这完全是对带宽和CPU的浪费。

  • 我不明白如何结合三个相互依赖的操作。 我有新用户需要保存到基地: > 从基础获取项目。 将第一次操作的数据保存到数据库中 保存到基本的新用户角色 最后,方法返回 到控制器。