簡體   English   中英

如果在 Intel Skylake CPU 上調用 function,為什么我的空循環運行速度會快兩倍?

[英]Why does my empty loop run twice as fast if called as a function, on Intel Skylake CPUs?

我正在運行一些測試來比較 C 和 Java 並遇到了一些有趣的事情。 在 main 調用的 function 中運行與優化級別 1 (-O1) 完全相同的基准代碼,而不是在 main 本身中,導致性能大約翻倍。 我正在打印 test_t 的大小,以毫無疑問地驗證代碼是否正在編譯為 x64。

我將可執行文件發送給正在運行 i7-7700HQ 的朋友並得到了類似的結果。 我正在運行 i7-6700。


這是較慢的代碼:

#include <stdio.h>
#include <time.h>
#include <stdint.h>

int main() {
    printf("Size = %I64u\n", sizeof(size_t));
    int start = clock();
    for(int64_t i = 0; i < 10000000000L; i++) {
        
    }
    printf("%ld\n", clock() - start);
    return 0;
}

而且更快:

#include <stdio.h>
#include <time.h>
#include <stdint.h>

void test() {
    printf("Size = %I64u\n", sizeof(size_t));
    int start = clock();
    for(int64_t i = 0; i < 10000000000L; i++) {
        
    }
    printf("%ld\n", clock() - start);
}

int main() {
    test();
    return 0;
}

我還將提供匯編代碼供您深入研究。 我不知道組裝。 慢點:

    .file   "dummy.c"
    .text
    .def    __main; .scl    2;  .type   32; .endef
    .section .rdata,"dr"
.LC0:
    .ascii "Size = %I64u\12\0"
.LC1:
    .ascii "%ld\12\0"
    .text
    .globl  main
    .def    main;   .scl    2;  .type   32; .endef
    .seh_proc   main
main:
    pushq   %rbx
    .seh_pushreg    %rbx
    subq    $32, %rsp
    .seh_stackalloc 32
    .seh_endprologue
    call    __main
    movl    $8, %edx
    leaq    .LC0(%rip), %rcx
    call    printf
    call    clock
    movl    %eax, %ebx
    movabsq $10000000000, %rax
.L2:
    subq    $1, %rax
    jne .L2
    call    clock
    subl    %ebx, %eax
    movl    %eax, %edx
    leaq    .LC1(%rip), %rcx
    call    printf
    movl    $0, %eax
    addq    $32, %rsp
    popq    %rbx
    ret
    .seh_endproc
    .ident  "GCC: (x86_64-posix-seh-rev0, Built by MinGW-W64 project) 8.1.0"
    .def    printf; .scl    2;  .type   32; .endef
    .def    clock;  .scl    2;  .type   32; .endef

快點:

    .file   "dummy.c"
    .text
    .section .rdata,"dr"
.LC0:
    .ascii "Size = %I64u\12\0"
.LC1:
    .ascii "%ld\12\0"
    .text
    .globl  test
    .def    test;   .scl    2;  .type   32; .endef
    .seh_proc   test
test:
    pushq   %rbx
    .seh_pushreg    %rbx
    subq    $32, %rsp
    .seh_stackalloc 32
    .seh_endprologue
    movl    $8, %edx
    leaq    .LC0(%rip), %rcx
    call    printf
    call    clock
    movl    %eax, %ebx
    movabsq $10000000000, %rax
.L2:
    subq    $1, %rax
    jne .L2
    call    clock
    subl    %ebx, %eax
    movl    %eax, %edx
    leaq    .LC1(%rip), %rcx
    call    printf
    nop
    addq    $32, %rsp
    popq    %rbx
    ret
    .seh_endproc
    .def    __main; .scl    2;  .type   32; .endef
    .globl  main
    .def    main;   .scl    2;  .type   32; .endef
    .seh_proc   main
main:
    subq    $40, %rsp
    .seh_stackalloc 40
    .seh_endprologue
    call    __main
    call    test
    movl    $0, %eax
    addq    $40, %rsp
    ret
    .seh_endproc
    .ident  "GCC: (x86_64-posix-seh-rev0, Built by MinGW-W64 project) 8.1.0"
    .def    printf; .scl    2;  .type   32; .endef
    .def    clock;  .scl    2;  .type   32; .endef

這是我的編譯批處理腳本:

@echo off
set /p file= File to compile: 
del compiled.exe
gcc -Wall -Wextra -std=c17 -O1 -o compiled.exe %file%.c
compiled.exe
PAUSE

對於編譯到匯編:

@echo off
set /p file= File to compile: 
del %file%.s
gcc -S -Wall -Wextra -std=c17 -O1 %file%.c
PAUSE

慢速版:

在此處輸入圖像描述

請注意sub rax, 1 \ jne對正好穿過..80的邊界(這是一個 32 字節的邊界)。 這是英特爾文檔中提到的有關此問題的案例之一,即如下圖:

在此處輸入圖像描述

所以這個 op/branch 對受到JCC 錯誤修復的影響(這會導致它不被緩存在 µop 緩存中)。 我不確定這是否是原因,還有其他事情在起作用,但這是一回事。

在快速版本中,分支沒有“觸及” 32 字節邊界,因此不受影響。

在此處輸入圖像描述

可能還有其他適用的效果。 仍然由於跨越 32 字節邊界,在較慢的情況下,循環分布在 µop 緩存中的 2 個塊上,即使沒有修復 JCC 錯誤,如果循環無法從循環執行,可能導致它在每次迭代中運行 2 個循環Stream 檢測器(在某些處理器上被其他錯誤的其他修復程序 SKL150 禁用)。 參見例如這個關於循環性能的答案。

為了解決各種評論說他們無法重現這一點,是的,有多種可能發生的方式:

  • 無論哪種影響導致速度變慢,都可能是由於操作/分支對跨 32 字節邊界的確切位置造成的,這純屬偶然。 從源代碼編譯不太可能重現相同的情況,除非您使用與原始發布者使用相同設置的相同編譯器。
  • 即使使用相同的二進制文件,無論哪個效果負責,奇怪的效果只會發生在特定的處理器上。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM