C 为什么在Linux中,动态链接的可执行文件明显比静态链接的慢?

C 为什么在Linux中,动态链接的可执行文件明显比静态链接的慢?,c,performance,static-linking,dynamic-linking,C,Performance,Static Linking,Dynamic Linking,用一个玩具程序测试,这个程序决定了tic-tac趾板的结果,我得到了这个。是什么造成了这么大的差异?我怀疑使用静态链接的libc调用rand会更快,但仍然对结果感到惊讶 ~$ gcc a.c -std=c11 -O3 ~$ time ./a.out 32614644 real 0m9.396s user 0m9.388s sys 0m0.004s ~$ gcc a.c -std=c11 -O3 -static ~$ time ./a.out 32614644 real

用一个玩具程序测试,这个程序决定了tic-tac趾板的结果,我得到了这个。是什么造成了这么大的差异?我怀疑使用静态链接的libc调用
rand
会更快,但仍然对结果感到惊讶

~$ gcc a.c -std=c11 -O3
~$ time ./a.out
32614644

real    0m9.396s
user    0m9.388s
sys     0m0.004s

~$ gcc a.c -std=c11 -O3 -static
~$ time ./a.out
32614644

real    0m6.891s
user    0m6.884s
sys     0m0.000s

#包括
#包括
#定义尺寸3
#定义大小_2(大小*大小)
静态整数确定结果(整数板[静态大小_2]){
对于(int i=0;i对于(inti=SIZE*2-2;i来说,这里的问题是您比较的是总执行时间,在这样一个小示例中,对于静态链接示例来说,它将非常优越,因为不需要进行查找

静态链接和动态链接的最大区别在于,动态链接有几个模块/对象,这些模块/对象在运行时链接在一起,静态编译的二进制文件包含二进制文件中的所有内容。当然,有些细节可能会有所不同,但大致上就是这样


因此……考虑到上述因素,启动一个可执行文件、加载几个不同的文件、执行函数并返回可能比加载文件并执行函数花费更多的时间。

如果不知道特定的ABI(取决于操作系统和cpu体系结构),我无法确定这是原因所在您的系统正在使用,但以下是最可能的解释

在大多数实现中,共享库(包括shared
libc.so
)中的代码必须是位置独立的代码。这意味着它可以在任何地址加载(而不是由链接器分配固定的运行时地址)因此不能在机器代码中使用硬编码的绝对数据地址。相反,它必须通过指令指针相对寻址或全局偏移表(GOT)访问全局数据其地址保存在寄存器中或相对于指令指针进行计算。这些寻址模式主要在设计良好的现代指令集体系结构(如x86_64、AARC64、RISC-V等)上有效。在大多数其他体系结构(包括32位x86)上,它们的效率非常低。例如,以下函数:

int x;
int get_x()
{
    return x;
}
将在x86上引出气球,如下所示:

get_x:
    push %ebp
    mov %esp, %ebp
    push %ebx
    sub $4, %esp
    call __x86.get_pc_thunk_bx
    add $_GLOBAL_OFFSET_TABLE_, %ebx
    mov x@GOT(%ebx), %eax
    mov (%eax),%eax
    add $4, %esp
    pop %ebx
    pop %ebp
    ret
鉴于您希望(对于非位置独立代码)看到:

由于随机数生成器具有内部(全局)状态,它们不得不为位置无关的代码跳这种昂贵的舞。由于它们所做的实际计算可能非常短且快速,PIC开销可能是它们运行时间的一个重要部分


确认这一理论的一种方法是尝试使用
rand\u r
random\u r
代替。这些函数使用调用方提供的状态,因此可以(至少在理论上)避免对全局数据进行任何内部访问。

您正在对加载时间进行基准测试,其中包括加载动态库。加载后,没有任何区别。只需在
main
中添加一个循环,该循环执行相关代码的次数非常多。@Olaf但加载时间是否超过2秒?分析两个可执行文件并找出原因。@AndrewHenle Profiling没有真正的帮助,它只是告诉我,
rand
或者更确切地说,
random\r
占用了大部分时间。对不起,我不是千里眼-在这方面工作。你为什么不试试呢?在你的程序中进行Profiling。差异确实在扩大。随着
N
的增加,执行时间现在是13.7秒静态为18.4秒,动态为18.4秒。在这种情况下,可能会在运行时对每个函数调用进行一些动态查找。+1不仅提供了一个合理的理论,而且为OP提供了一个简单的方法来测试该理论。你完全正确。我使用的是Ubuntu的i386版本。
get_x:
    push %ebp
    mov %esp, %ebp
    push %ebx
    sub $4, %esp
    call __x86.get_pc_thunk_bx
    add $_GLOBAL_OFFSET_TABLE_, %ebx
    mov x@GOT(%ebx), %eax
    mov (%eax),%eax
    add $4, %esp
    pop %ebx
    pop %ebp
    ret
get_x:
    mov x, %eax
    ret