有什么想法导致这种奇怪的性能下降似乎取决于寄存器的选择吗?

问题描述 投票:0回答:1

我一直在尝试进一步优化这个展开的循环:

some_loop:
UNROLL_CNT = 256                  ; best for Core i9-9900k at least
UNROLL_I = 0
repeat UNROLL_CNT
    ;
    ; Block 1
    ;
    movzx r9, byte ptr[r10]       ; tmp = s[i]
    add r11b, r9b                 ; j += s[i]
    movzx rax, byte ptr[r11]      ; s[i] = s[j]
    mov byte ptr[r10], al         ;
    mov byte ptr[r11], r9b        ; s[j] = tmp

    add r9b, al                   ; tmp = s[i] + s[j]
    inc r10b                      ; i++
    movzx rax, byte ptr[rsp + r9] ; rax = s[tmp]
    xor byte ptr [rcx+UNROLL_I], al ; *data ^= rax => *data ^= s[s[i] + s[j]]
UNROLL_I = UNROLL_I + 1
endm

..我想我应该尝试将 2 个迭代交错在一起(使用一组额外的寄存器),但在我到达那里之前,令我惊讶的是,仅通过使用新的寄存器组,性能就下降了一半(? ):

some_loop:
UNROLL_CNT = 256
UNROLL_I = 0
repeat UNROLL_CNT/2
    ;
    ; Block 1
    ;
    movzx r9, byte ptr[r10]       ; tmp = s[i]
    add r11b, r9b                 ; j += s[i]
    movzx rax, byte ptr[r11]      ; s[i] = s[j]
    mov byte ptr[r10], al         ;
    mov byte ptr[r11], r9b        ; s[j] = tmp

    add r9b, al                   ; tmp = s[i] + s[j]
    add r10b, 2                   ; i+=2 (for next iter.)
    movzx rax, byte ptr[rsp + r9] ; al= s[tmp]
    xor byte ptr [rcx+UNROLL_I], al ; *data ^= s[s[i] + s[j]]
UNROLL_I = UNROLL_I + 1

    ;
    ; Block 2
    ;  same as above with r9->r12, r10->r13, r11->r14, rax->r15
    ;
    movzx r12, byte ptr[r13]      ; tmp = s[i]
    add r14b, r12b                ; j += s[i]
    movzx r15, byte ptr[r14]      ; s[i] = s[j]
    mov byte ptr[r13], r15b       ;
    mov byte ptr[r14], r12b       ; s[j] = tmp

    add r12b, r15b                ; tmp = s[i] + s[j]
    add r13b, 2                   ; i+=2 (for next iter.)
    movzx r15, byte ptr[rsp + r12] ; r15b = s[tmp]
    xor byte ptr [rcx+UNROLL_I], r15b ; *data ^= s[s[i] + s[j]]
UNROLL_I = UNROLL_I + 1
endm

因此,第一个列表重复 256 次 x 块 1,第二个列表重复 128 次 x(块 1,块 2)。正如您所看到的,除了使用的寄存器以及变成

inc r10b
add r10b, 2
指令之外,指令实际上是相同的。我预计,这将使清单 1 在性能方面等同于清单 2。

我正在寻找有关特定 GP 寄存器的任何参考,这些寄存器比 x86-64 上的其他寄存器更快,但似乎没有任何参考,这让我认为这不是寄存器问题,而是一些缓存/并行化问题(尽管每个块上的指令都非常紧密地相互依赖;不确定它将如何执行并行化)。

当将 Block 2 引入图片中时,您是否知道什么可能会导致性能极端下降?

如果您对如何进一步优化 Block 1 本身有任何其他见解,那也非常受欢迎,因为这是我的最终目标,而不是交错的东西(我什至不确定它会提高整体性能) .

我在第二个列表中尝试过的事情:

  • 减少
    UNROLL_CNT
    :性能提升至
    UNROLL_CNT = 32
    ,此时仍比单块列表慢 1.5 倍左右。
  • add r10b/r13b, 2
    替换为
    inc r10b/r13b
    :正如预期的那样,没有太大区别。
  • 用与第一个块相同的块(相同的寄存器)替换第二个块:并且,正如预期的那样,性能恢复了(这只是为了理智)
  • 编辑:还尝试用 r12、r13、r14、r15 替换单块版本中的寄存器:性能很快,所以这绝对不是寄存器问题。

顺便说一句,如果您想知道问题是否与循环条件有关,这里是:

    add rcx, UNROLL_CNT           ; data += UNROLL_CNT
    dec rbx                       ; if --num_unrolled_loops
    jnz some_loop                 ;   continue

正如你所看到的,它在 rbx 上,在展开的循环体上根本没有使用它。

assembly optimization x86 x86-64
1个回答
0
投票

尝试这个代码,我还没有测试过它,但是如果你将它分成每个循环的 4 部分,因为 256 可以被 4 整除,这应该会更快。

some_loop:
    UNROLL_CNT = 256
    UNROLL_I = 0

    repeat UNROLL_CNT/4   ; Unroll the loop further
    ;
    ; Block 1
    ;
        movzx r8, byte ptr [r10]       ; tmp = s[i]
        add r11b, r8b                  ; j += s[i]
        movzx rax, byte ptr [r11]      ; s[i] = s[j]
        mov byte ptr [r10], al         ;
        mov byte ptr [r11], r8b        ; s[j] = tmp

        add r8b, al                    ; tmp = s[i] + s[j]
        inc r10b                       ; i++
        movzx rax, byte ptr [rsp + r8] ; rax = s[tmp]
        xor byte ptr [rcx + UNROLL_I], al ; *data ^= rax => *data ^= s[s[i] + s[j]]

        ; Repeat the block for the next set of unrolled instructions
        movzx r8, byte ptr [r10]       
        add r11b, r8b                  
        movzx rax, byte ptr [r11]      
        mov byte ptr [r10], al         
        mov byte ptr [r11], r8b        

        add r8b, al                    
        inc r10b                       
        movzx rax, byte ptr [rsp + r8] 
        xor byte ptr [rcx + UNROLL_I + 1], al ;

        ; Repeat the block for the next set of unrolled instructions
        movzx r8, byte ptr [r10]       
        add r11b, r8b                  
        movzx rax, byte ptr [r11]      
        mov byte ptr [r10], al         
        mov byte ptr [r11], r8b        

        add r8b, al                    
        inc r10b                       
        movzx rax, byte ptr [rsp + r8] 
        xor byte ptr [rcx + UNROLL_I + 2], al ;

        ; Repeat the block for the next set of unrolled instructions
        movzx r8, byte ptr [r10]       
        add r11b, r8b                  
        movzx rax, byte ptr [r11]      
        mov byte ptr [r10], al         
        mov byte ptr [r11], r8b        

        add r8b, al                    
        inc r10b                       
        movzx rax, byte ptr [rsp + r8] 
        xor byte ptr [rcx + UNROLL_I + 3], al ;

        ; Update UNROLL_I for the next iteration
        UNROLL_I = UNROLL_I + 4

我的代码的目的

  1. 使用通用寄存器而不是使用特定的寄存器如r9和r11,使用通用寄存器(rax、rbx、rcx等)给编译器在寄存器分配方面有更多的自由。

  2. 内存访问优化最大限度地减少内存访问。如果可能,将值加载到寄存器中并执行多个操作,然后再存储回内存。

© www.soinside.com 2019 - 2024. All rights reserved.