Performance 小型阵列的最快偏移量读取

Performance 小型阵列的最快偏移量读取,performance,assembly,x86,cpu-architecture,intrinsics,Performance,Assembly,X86,Cpu Architecture,Intrinsics,为了提高速度,我想读取由第9个寄存器中的值引用的8个寄存器中的一个。 我看到的实现这一点的最快方法是使用3个条件跳转(在第9行中检查3位) 登记册)。这应该比使用偏移量执行此操作的标准方法具有更短的延迟 内存读取,但这仍然需要至少6个时钟周期(至少一个测试加一个 条件jmp每比特检查) 是否有任何内置的商用CPU(最好是x86/x64)来执行此“偏移寄存器” 读取“延迟仅为一个时钟周期?” 理论上,一个经过优化的CPU可以通过一次加法和一次移动来实现这一点,因此需要两个或一个时钟 周期似乎很容易

为了提高速度,我想读取由第9个寄存器中的值引用的8个寄存器中的一个。 我看到的实现这一点的最快方法是使用3个条件跳转(在第9行中检查3位) 登记册)。这应该比使用偏移量执行此操作的标准方法具有更短的延迟 内存读取,但这仍然需要至少6个时钟周期(至少一个测试加一个 条件jmp每比特检查)

是否有任何内置的商用CPU(最好是x86/x64)来执行此“偏移寄存器” 读取“延迟仅为一个时钟周期?”

理论上,一个经过优化的CPU可以通过一次加法和一次移动来实现这一点,因此需要两个或一个时钟 周期似乎很容易…架构不关心速度有什么一般原因吗
为一个小数组增加一个偏移量读取?

将CPU寄存器作为一个数组来处理,这在当今确实不是一种常见的方法。我所知道的最后一种架构是PDP11,它在80年代末消失了。为什么不像其他数组一样将数组放入某个内存位置

也就是说,您可以使用计算的跳转。这还将数据依赖项(索引寻址模式)替换为控制依赖项,因此无序的exec甚至不必等到索引输入就绪后才能开始运行使用最终RAX的代码。当然,这假设了正确的分支预测,如果索引经常更改,这是不可能的。分支预测失误会花费许多周期的少量工作,但L1d缓存中的负载延迟很小,很容易与独立工作重叠

吞吐量成本高于内存中的数组:一些地址计算、一次跳转、一次移动和
ret
,而不仅仅是
mov
,甚至是具有索引寻址模式的内存操作数

要内联此代码,只需将
jmp*%rax
替换为
call*%rax
,另一个uop的成本就可以了。或者将
ret
指令替换为底部标签上的
jmp
,并将跳转表的步幅增加到8,以考虑更长的编码时间

    # select a register from r8...r15 according to the value in rdi
select:
    lea labels-4*8(%rip),%rax # rdi = 8 is the first jump table entry
    lea (%rax,%rdi,4),%rax    # pointer to the appropriate entry
    jmp *%rax                 # computed jump

    .align 4
labels:
    mov %r8, %rax
    ret

    .align 4
    mov %r9, %rax
    ret

    .align 4
    mov %r10, %rax
    ret

    .align 4
    mov %r11, %rax
    ret

    .align 4
    mov %r12, %rax
    ret

    .align 4
    mov %r13, %rax
    ret

    .align 4
    mov %r14, %rax
    ret

    .align 4
    mov %r15, %rax
    ret

虽然这可能比三次条件跳转(取决于访问模式)快,但它肯定不会超过仅使用数组。

将CPU寄存器视为数组在当今已不是一种常见的方法。我所知道的最后一种架构是PDP11,它在80年代末消失了。为什么不像其他数组一样将数组放入内存位置呢?通过变量索引访问寄存器或多或少会破坏寄存器重命名,因此对于快速处理器来说,这是一个不太可能的功能。您可以使用VPERMD对YMM寄存器进行排序,也许这对于您的用例来说还可以?@fuz AVR也可以这样做;寄存器映射到地址空间。CPU没有intrinics;编译器具有内在特性。也许你指的是“机器指令”或“寻址模式”(编译器可能有其内在的功能)?正如前面的评论者所说,大多数ISA不能使用运行时索引对寄存器进行索引,只能使用嵌入在指令机器代码中的索引。(因此,寄存器提取可以在解码后的任何时间进行,而不需要作为ISA的一部分进行任何额外的间接寻址。)自修改代码比仅仅从数据缓存中刷新一行要昂贵得多;在现代x86上,它刷新整个管道(perf counter event
machine\u clears.smc
)。如果您JIT一次并多次运行,那么这很好,如果您每次都这样做,则会产生完全的垃圾。在现代Intel上,存储转发延迟仅为3到5个周期,类似于4到5个周期的L1d加载使用延迟。在需要低延迟阵列索引的地方,您试图解决的更大问题是什么?你能用AVX2洗牌来解决这个问题吗?这是一个3c延迟(加上整数regs索引的
vmovd
)的好主意。我希望有一个C++方式来生成这样的程序集。另外,我还不相信(现在?)对于随机访问,这会比一级缓存慢。@bobuhito Agner的表格中对Skylake X是这样说的。您的里程可能会有所不同。如果您告诉我们您试图使用这种方法实现什么算法,我们可能会提出更好的解决方案,打破“使用寄存器作为数组”的想法。@bobuhito:是的,SnB系列的L1d加载使用延迟为4个周期(最佳情况)。对于值来自另一个负载的简单寻址模式,或在外部5个周期。但是核心不会在这5个循环中暂停!!几乎总是有一些独立的工作要做。负载吞吐量为每周期2次(在现代x86、AMD和Intel上)。@bobuhito Hm。。。我会用状态机来做这个。您计划如何处理其他不增加的值?显然,这不是全部情况。也许发布一些你想要实现的整个操作的伪代码(而不仅仅是你想要使用间接寻址的小部分)。@bobuhito你的问题描述对我来说也有点抽象。最好是在你的问题中添加一段自包含的代码来演示你想要实现的目标。然后我可以尝试将其重写为快速汇编。如果你想做一些简单的计算,考虑一下你是否真的需要执行所有256次迭代,或者如果你可以使用一些和公式。