为什么GCC在printf调用之前插入看似非必要的指令?

spa*_*cus 1 x86 assembly gcc

我正在尝试自己学习x86,我决定剖析一个简单的c程序,看看GCC输出什么.该计划是这样的:

#include <stdio.h>
int main() {
  printf("%s","Hello World");
  return 0;
}
Run Code Online (Sandbox Code Playgroud)

我用-S编译了代码,然后剥离了我发现不必要的东西,并将汇编代码减少到了这个.

.pfArg:
.string "%s"
.text

.Hello:
.string "Hello World"
.text

.globl main
.type   main, @function

main:
pushq   %rbp        # push what was in base pointer onto stack
movq    %rsp, %rbp  # move stack pointer to base pointer
subq    $16, %rsp   # subtract 16 from sp and store in stack pointer

# prepare arguments for printf
movl    $.Hello, %esi   # put & of "Hello World" into %esi
movq    $.pfArg, %rdi   # put & of "%d" into %eax
call    printf
leave
ret
Run Code Online (Sandbox Code Playgroud)

现在几乎上面代码中的所有内容都对我有意义,除了前两个主要内容.虽然这是我得到的,而不是剥离的东西.

.LC0:
    .string "%s"

.LC1:
    .string "Hello World"
    .text

.globl main
    .type   main, @function

main:

.LFB0:
    pushq   %rbp        # push what was in base pointer onto stack
    movq    %rsp, %rbp  # move stack pointer to base pointer

  # prepare arguments for printf
    movl    $.LC0, %eax # put arg into %eax
    movl    $.LC1, %esi # put second arg into %esi
    movq    %rax, %rdi  # move value in %rax to %rdi ???? ( why not just put $.LCO into %rax directly )
    movl    $0, %eax    # clear out %eax ???? ( why do we need to clear it out )
    call    printf      
    movl    $0, %eax    # return 0
    leave
    ret

.LFE0:
    .size   main, .-main
    .ident  "GCC: (Ubuntu/Linaro 4.5.2-8ubuntu4) 4.5.2"
    .section    .note.GNU-stack,"",@progbits
Run Code Online (Sandbox Code Playgroud)

我用2标记了2个指令?我不明白

第一条指令是将%rax中的内容移动到%rdi中以准备printf调用.这很好,除了我们只是将$ .LC0(字符串"%s")移动到%eax中.这似乎是不必要的,为什么我们不首先将$ .LC0移动到%rdi而不是将其移动到%eax然后转移到%rdi?

第二条指令是清除%eax,我理解它是函数的返回值.但是,如果该功能只是破坏它,那么为什么海湾合作委员会要小心清除它呢?

srk*_*ing 7

一条经验法则:

  1. 如果你担心有效的代码,不要费心去看未经优化的输出.
  2. 始终衡量,永远不要假设您在汇编语言级别的"改进"提高了性能.

即使在优化的代码中,当没有功能需要破坏寄存器时,您可能会看到看似不必要的指令,例如"xor%eax,%eax".这些指令通过告知管道在该点之外不存在该寄存器的数据依赖性而发挥特殊作用.在现代无序处理器中,核心的流水线推测性地在当前EIP之前执行许多指令.以这种方式明确地减少数据依赖性有助于推测机制,并且可以提高紧密循环中的性能,尤其是.

在其他情况下,编译器显然可能采用循环方法,而实际上它正在尝试将手头的工作与目标核心管道中可用的并行执行单元进行匹配.并行调度的更多指令通常比序列化的指令更少完成.

如果您真的想要挤出最后一滴性能,请在代码块之前和之后使用rdtsc指令来测量消耗的时钟数.要小心一点,因为rdtsc并没有严格按照周围的说明进行排序,但实际上测量它对1000个时钟范围内的任何东西来说都是准确的.