繁体   English   中英

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

[英]Why is a dynamically linked executable noticeably slower than the statically linked one in Linux?

用玩具程序测试确定井字棋盘的结果,我得到了这个。 是什么让这个差别很大? 我怀疑使用静态链接的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

#include <stdio.h>
#include <stdlib.h>

#define SIZE 3
#define SIZE_2 (SIZE * SIZE)

static int determineResult(int board[static SIZE_2]) {
  for (int i = 0; i < SIZE_2; i += SIZE) {
    if (!board[i]) {
      continue;
    }
    for (int j = i + 1; j < i + SIZE; ++j) {
      if (board[i] != board[j]) {
        goto next;
      }
    }
    return board[i];
  next:;
  }
  for (int i = 0; i < SIZE; ++i) {
    if (!board[i]) {
      continue;
    }
    for (int j = i + SIZE; j < i + SIZE_2; j += SIZE) {
      if (board[i] != board[j]) {
        goto next2;
      }
    }
    return board[i];
  next2:;
  }
  for (int i = SIZE + 1; i < SIZE_2; i += SIZE + 1) {
    if (board[i] != *board) {
      goto next3;
    }
  }
  return *board;
next3:
  for (int i = SIZE * 2 - 2; i <= SIZE_2 - SIZE; i += SIZE - 1) {
    if (board[i] != board[SIZE - 1]) {
      return 0;
    }
  }
  return board[SIZE - 1];
}

#define N 50000000

int main(void) {
  srand(0);
  size_t n = 0;
  for (int i = 0; i < N; ++i) {
    int board[SIZE_2];
    for (int i = 0; i < SIZE_2; ++i) {
      board[i] = rand() % 3;
    }
    n += determineResult(board);
  }
  printf("%zu\n", n);
  return EXIT_SUCCESS;
}

我不能确定这是不知道您的系统正在使用的特定ABI(这取决于操作系统和CPU架构)的原因,但以下是最可能的解释。

在大多数实现中,共享库中的代码(包括共享libc.so )必须是与位置无关的代码 这意味着它可以在任何地址加载(而不是由链接器分配固定的运行时地址),因此不能在机器代码中使用硬编码的绝对数据地址。 相反,它必须通过指令指针相对寻址或全局偏移表 (GOT)访问全局数据,其地址保存在寄存器中或相对于指令指针计算。 这些寻址模式主要在精心设计的现代指令集架构(如x86_64,AArch64,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

而你会期望(对于非位置无关的代码)看到:

get_x:
    mov x, %eax
    ret

由于随机数生成器具有内部(全局)状态,因此他们无法为与位置无关的代码执行这种昂贵的舞蹈。 由于他们所做的实际计算可能非常短且快,因此PIC开销可能是其运行时间的重要部分。

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

这里的问题是你正在比较总的执行时间,在这样的小例子中,它将非常优于静态链接示例,因为没有要执行的查找。

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

所以......考虑到上述因素,启动可执行文件,加载几个不同的文件,执行函数并返回可能比加载文件和执行函数需要更多的时间。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM