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

时间:2015-09-12 23:59:49

标签: c performance static-linking dynamic-linking

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

2 个答案:

答案 0 :(得分:3)

我不能确定这是不知道您的系统正在使用的特定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。这些函数使用调用者提供的状态,因此(至少在理论上)可以避免对全局数据的任何内部访问。

答案 1 :(得分:0)

这里的问题是你要比较总的执行时间,在这样一个小例子中它对于静态链接示例来说会非常优越,因为没有要进行的查找。

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

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