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

短循环延迟

爱繁
2023-03-14

我在试着理解为什么一些简单的环路会以这样的速度运行

第一种情况:

L1:
    add rax, rcx  # (1)
    add rcx, 1    # (2)
    cmp rcx, 4096 # (3)
    jl L1

根据IACA,吞吐量是1个周期,瓶颈是端口1,05。我不明白为什么它是1 cylce。毕竟我们有两个循环承载的依赖关系:

(1) -> (1) ( Latancy is 1) 
(2) -> (2), (2) -> (1), (2) -> (3) (Latency is 1 + 1 + 1).

而且这种Latency是循环进行的,所以它应该会减慢我们的迭代。

Throughput Analysis Report
--------------------------
Block Throughput: 1.00 Cycles       Throughput Bottleneck: Port0, Port1, Port5

Port Binding In Cycles Per Iteration:
-------------------------------------------------------------------------
|  Port  |  0   -  DV  |  1   |  2   -  D   |  3   -  D   |  4   |  5   |
-------------------------------------------------------------------------
| Cycles | 1.0    0.0  | 1.0  | 0.0    0.0  | 0.0    0.0  | 0.0  | 1.0  |
-------------------------------------------------------------------------


| Num Of |              Ports pressure in cycles               |    |
|  Uops  |  0  - DV  |  1  |  2  -  D  |  3  -  D  |  4  |  5  |    |
---------------------------------------------------------------------
|   1    | 1.0       |     |           |           |     |     | CP | add rax, rcx
|   1    |           | 1.0 |           |           |     |     | CP | add rcx, 0x1
|   1    |           |     |           |           |     | 1.0 | CP | cmp rcx, 0x1000
|   0F   |           |     |           |           |     |     |    | jl 0xfffffffffffffff2
Total Num Of Uops: 3

第二种情况:

L1:    
    add rax, rcx
    add rcx, 1
    add rbx, rcx
    cmp rcx, 4096
    jl L1
Block Throughput: 1.65 Cycles       Throughput Bottleneck: InterIteration

Port Binding In Cycles Per Iteration:
-------------------------------------------------------------------------
|  Port  |  0   -  DV  |  1   |  2   -  D   |  3   -  D   |  4   |  5   |
-------------------------------------------------------------------------
| Cycles | 1.4    0.0  | 1.4  | 0.0    0.0  | 0.0    0.0  | 0.0  | 1.3  |


| Num Of |              Ports pressure in cycles               |    |
|  Uops  |  0  - DV  |  1  |  2  -  D  |  3  -  D  |  4  |  5  |    |
---------------------------------------------------------------------
|   1    | 0.6       | 0.3 |           |           |     |     |    | add rax, rcx
|   1    | 0.3       | 0.6 |           |           |     |     | CP | add rcx, 0x1
|   1    | 0.3       | 0.3 |           |           |     | 0.3 | CP | add rbx, rcx
|   1    |           |     |           |           |     | 1.0 | CP | cmp rcx, 0x1000
|   0F   |           |     |           |           |     |     |    | jl 0xffffffffffffffef

我更不明白为什么吞吐量是1.65。

共有1个答案

池恩
2023-03-14

在第一个循环中,有两条dep链,一条用于rax,另一条用于rcx。

add rax, rcx  # depends on rax and rcx from the previous iteration, produces rax for the next iteration

add rcx, 1    # latency = 1

add rcx,1的2周期延迟dep链-

在任何给定的迭代中,只需要前一个迭代的结果就可以生成此迭代的结果。迭代中没有循环携带的依赖项,只有在迭代之间。

正如我几天前在回答您的问题时所解释的,cmp/jcc不是环载dep链的一部分。

如果cmovsetcc读取它生成的标志输出,则cmp仅是dep链的一部分。控制依赖关系是预测的,而不是等待类似的数据依赖关系。

实际上,在我的E6600(第一代Core2,我目前没有SnB可用)上:

; Linux initializes most registers to zero on process startup, and I'm lazy so I depended on this for this one-off test.  In real code, I'd xor-zero ecx
    global _start
_start:
L1:
    add eax, ecx        ; (1)
    add ecx, 1          ; (2)
    cmp ecx, 0x80000000 ; (3)
    jb L1            ; can fuse with cmp on Core2 (in 32bit mode)

    mov eax, 1
    int 0x80

我将其移植到32位,因为Core2只能在32位模式下进行宏融合,并使用了jb,因为Core2只能对无符号分支条件进行宏融合。我使用了一个大的循环计数器,所以我不需要在此之外再使用一个循环。(IDK为什么选择像4096这样的小循环计数。你确定你没有从短循环之外的其他东西中测量额外的开销吗?)

$ yasm -Worphan-labels -gdwarf2 -felf tinyloop.asm && ld -m elf_i386 -o tinyloop tinyloop.o
$ perf stat -e task-clock,cycles,instructions,branches ./tinyloop

Performance counter stats for './tinyloop':

    897.994122      task-clock (msec)         #    0.993 CPUs utilized          
 2,152,571,449      cycles                    #    2.397 GHz                    
 8,591,925,034      instructions              #    3.99  insns per cycle        
 2,147,844,593      branches                  # 2391.825 M/sec                  

   0.904020721 seconds time elapsed

因此,它以每周期3.99 INSN的速度运行,这意味着每周期约有一次迭代。

如果你的Ivybridge运行的代码速度只有你的一半,我会感到惊讶。更新:根据聊天中的讨论,是的,似乎IVB实际上只获得2.14 IPC。(每1.87c迭代一次)。将添加rax、rcx更改为添加rax、rbx或其他内容,以消除上一次迭代对循环计数器的依赖性,从而将吞吐量提高到3.8 IPC(每1.05c迭代一次)。我不明白为什么会这样。

对于一个不依赖于宏融合的类似循环,(add/inc-ecx/jnz)我还可以每1c进行一次迭代。(每个周期2.99 INSN)。

然而,在同样读取ecx的循环中有第四个insn会大大降低速度。Core2可以每个时钟发出4个UOP,即使(像SnB/IvB)它只有三个ALU端口。(很多代码都包含内存UOP,因此这是有意义的。)

add eax, ecx       ; changing this to add eax,ebx  helps when there are 4 non-fusing insns in the loop
; add edx, ecx     ; slows us down to 1.34 IPC, or one iter per 3c
; add edx, ebx     ; only slows us to 2.28 IPC, or one iter per 1.75c
                   ; with neither:    3    IPC, or one iter per 1c
inc ecx
jnz L1             # loops 2^32 times, doesn't macro-fuse on Core2

我预计仍将以3 IPC运行,或每4/3=1.333c运行一台iter。然而,前SnB CPU有更多的瓶颈,如ROB读取和寄存器读取瓶颈。SnB切换到物理寄存器文件消除了这些减速。

在您的第二个循环中,IDK为什么它没有在每1.333c运行一次迭代。insn更新rbx直到该迭代的其他指令之后才能运行,但这就是乱序执行的目的。您确定它与每1.85个周期一次迭代一样慢吗?您使用perf进行了测量,以获得足够高的计数以获得有意义的数据?(rdtsc周期计数不准确,除非您禁用turbo和频率缩放,但perf计数器仍然计算实际的核心周期)。

我不希望它与

L1:    
    add rax, rcx
    add rbx, rcx      # before/after inc rcx shouldn't matter because of out-of-order execution
    add rcx, 1
    cmp rcx, 4096
    jl L1
 类似资料:
  • 问题内容: 因此,当我运行此代码时,我的JFrame变得无响应。我设法将其追溯到gameLoop()下的while循环。无论使用其中调用Thread.sleep()的delay(1000 / FRAMERATE),它都不允许键或鼠标侦听器执行其工作。 下面的完整代码,gameLoop()中存在问题 如果很重要,程序将从此处开始,然后转到Game类 可能无关紧要,但这是github存储库的插件htt

  • 如何在不停止主GUI线程的情况下停止循环?(线程。睡眠(1000)未工作)

  • 问题内容: 我正在使用带有node_redis的Node.js,并遍历一个对象并在Redis中查找数据,然后返回结果。 我有这样的设置: 问题在于,它会在完成对redis的调用之前循环通过。因此,在实际更新总值之前,将调用回调。由于延迟,它似乎也跳过了一些项目。 有没有更好的方法来解决这个问题? 谢谢! 编辑: 好的,所以我这样更新了它: 这似乎可行,它在适当的时间触发了回调,但是似乎只有最后一个

  • 问题内容: 我想在循环内添加延迟/睡眠: 我这样尝试过: 只有第一种情况是正确的:显示后,它将等待3秒钟,然后显示,但随后将不断重复。 我想要的是在显示3秒之后显示出来,然后它需要第二次等待3秒,依此类推。 问题答案: 该函数是非阻塞的,将立即返回。因此,您的循环将非常快速地迭代,并且将快速连续地发起3秒超时触发。这就是为什么您的第一个警报会在3秒钟后弹出,而其余所有警报都将连续不断地出现。 您可

  • 问题内容: 我使用以下代码为基于RotatedTranstion的ImageView创建了动画: 这将产生以下动画: 轮换行动 就像您在动画gif中注意到的那样,动画不是连续的,即在动画周期之间存在很小的延迟(暂停)。 我试图看一下API,但无法弄清楚是什么原因造成了这种延迟以及如何摆脱这种延迟。 问题答案: 每个周期之间的明显停顿是由内插器引起的,默认情况下会使用该插值器(因此它在每个周期的末尾

  • 问题内容: 因此,我将顺序的ajax链接在一起,以按顺序加载URL数组。最初,我使用代替,并且无论哪种方式都可以正常工作- 只要所有网址都存在。但是,由于可能会丢失文件,因此我想对此进行补偿,然后,最后通知用户丢失了哪些文件,以便更轻松地进行纠正。 但是,问题是,在丢失的文件/ 404上,代码会像应执行的那样执行,但随后退出循环,从而阻止了进一步的ajax调用。因此,我认为,我需要某种方式来处理并