gpt4 book ai didi

c - 同一个C程序的三个版本,为什么第一个这么快?

转载 作者:太空宇宙 更新时间:2023-11-04 01:16:23 24 4
gpt4 key购买 nike

这是一个非常简单的 C 程序:

int main()
{
int n = 0;

while(n != 1000000000){
n += 1;
}

return n;

}

我用 Clang 编译了它并对其进行了计时。它在 4.711095243692398e-06 秒或 0.000004711095243692398 秒内运行。

接下来,我使用 Godbolt 编译器资源管理器 (https://godbolt.org) 将 C 程序输出为英特尔语法汇编语言,以删除 .cfi 指令:

.file   "Svx.c"
.intel_syntax noprefix
.text
.globl main
.type main, @function
main:
push rbp
mov rbp, rsp
mov DWORD PTR -4[rbp], 0
jmp .L2
.L3:
add DWORD PTR -4[rbp], 1
.L2:
cmp DWORD PTR -4[rbp], 1000000000
jne .L3
mov eax, DWORD PTR -4[rbp]
pop rbp
ret
.size main, .-main
.ident "GCC: (Ubuntu 7.4.0-1ubuntu1~18.04.1) 7.4.0"
.section .note.GNU-stack,"",@progbits

我用 GCC 编译它并计时。结果是 1.96 秒——比 Clang 版本慢得多。

最后,我创建了自己的汇编版本:

[BITS 64]
[default rel]

global main:function

section .data align=16

section .text

main:
xor rax,rax
l_01:
cmp rax,1000000000
je l_02
add rax,5
jmp l_01
l_02:
ret

我用 nasm 编译它并用 ld 链接它:

sudo nasm -felf64 Svx.asm

sudo ld -shared Svx.o -o Svx.so

并计时。它在 0.14707629615440965 秒内运行。

如果反向编译版本运行速度慢得多(0.0000047 秒 vs 1.96 秒)而我的 NASM 版本运行在 中,为什么 C 版本运行得如此之快0.147 秒?我感觉 C 版本在 0.0000047 秒的结果是错误的;这似乎快得不可思议。这是它对汇编语言的 Clang 输出:

    .text
.intel_syntax noprefix
.file "Svx.c"
.globl main # -- Begin function main
.p2align 4, 0x90
.type main,@function
main: # @main
.cfi_startproc
# %bb.0:
push rbp
.cfi_def_cfa_offset 16
.cfi_offset rbp, -16
mov rbp, rsp
.cfi_def_cfa_register rbp
mov dword ptr [rbp - 4], 0
.LBB0_1: # =>This Inner Loop Header: Depth=1
cmp dword ptr [rbp - 4], 1000000000
je .LBB0_3
# %bb.2: # in Loop: Header=BB0_1 Depth=1
mov eax, dword ptr [rbp - 4]
add eax, 1
mov dword ptr [rbp - 4], eax
jmp .LBB0_1
.LBB0_3:
mov eax, dword ptr [rbp - 4]
pop rbp
.cfi_def_cfa rsp, 8
ret
.Lfunc_end0:
.size main, .Lfunc_end0-main
.cfi_endproc
# -- End function
.ident "clang version 8.0.0-3~ubuntu18.04.1 (tags/RELEASE_800/final)"
.section ".note.GNU-stack","",@progbits
.addrsig

列表显示他们使用变量堆栈,而不是寄存器,这(通常)速度较慢。

0.0000047 秒的速度,数到十亿似乎快得不可思议。如果这个速度是正确的,它的 secret 是什么?逆向工程没有揭示任何东西,事实上 Godbolt 版本要慢得多。

最佳答案

Clang 只是意识到此循环运行了 1000000000 次,并执行了相当于 return 1000000000; 的操作。

我使用 -O3 指定您正在使用的输出:

        .text
.file "test.c"
.globl main # -- Begin function main
.p2align 4, 0x90
.type main,@function
main: # @main
.cfi_startproc
# %bb.0:
movl $1000000000, %eax # imm = 0x3B9ACA00
retq
.Lfunc_end0:
.size main, .Lfunc_end0-main
.cfi_endproc
# -- End function

.ident "clang version 8.0.0 (tags/RELEASE_800/final)"
.section ".note.GNU-stack","",@progbits
.addrsig

注意 main 的内容:

# %bb.0:
movl $1000000000, %eax # imm = 0x3B9ACA00
retq

这将完全删除循环并只返回 1000000000

解决这个问题的一个技巧是使用volatile:

int main(void)
{
volatile int n = 0;

while(n != 1000000000) {
n += 1;
}

return n;
}

输出(同样是 -O3):

        .text
.file "test.c"
.globl main # -- Begin function main
.p2align 4, 0x90
.type main,@function
main: # @main
.cfi_startproc
# %bb.0:
movl $0, -4(%rsp)
movl -4(%rsp), %ecx
movl -4(%rsp), %eax
cmpl $1000000000, %ecx # imm = 0x3B9ACA00
je .LBB0_3
.p2align 4, 0x90
.LBB0_1: # =>This Inner Loop Header: Depth=1
addl $1, %eax
movl %eax, -4(%rsp)
movl -4(%rsp), %ecx
movl -4(%rsp), %eax
cmpl $1000000000, %ecx # imm = 0x3B9ACA00
jne .LBB0_1
.LBB0_3:
retq
.Lfunc_end0:
.size main, .Lfunc_end0-main
.cfi_endproc
# -- End function

.ident "clang version 8.0.0 (tags/RELEASE_800/final)"
.section ".note.GNU-stack","",@progbits
.addrsig

关于c - 同一个C程序的三个版本,为什么第一个这么快?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58050604/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com