簡體   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