在 Linux 上加载共享库会产生多少运行时开销

How much runtime overhead is incurred by the loading of shared libraries on Linux

我正在搜索有关使用 运行time 链接器(例如 ld.so)加载程序时发生的 运行time 时间开销的一些统计信息。我不是 运行time 链接器如何工作的专家,但据我所知,它通常执行以下操作:

因此,当我通过 GUI 或命令行启动程序时,有时会发生对 exec 的系统调用并启动请求的程序。让我们快速看看接下来会发生什么:

  1. Exec(myprogram)
  2. 操作系统将 myprogramm 加载到内存中
  3. 操作系统将执行移交给_start
  4. 一些初始化发生并且 运行时间链接器是 运行
  5. main()被称为

假设上面的列表是正确的并且我没有遗漏任何主要步骤我会对两件事感兴趣:

  1. 根据理论,第 4 步的开销是多少?
  2. 我如何在实践中确定步骤 4 的开销。(例如,对于 Firefox 或 Chrome 等真实程序)?

为了粗略估计,编写两个测试程序:一个静态程序和一个使用动态库(变体:仅C运行时动态库)的程序。然后测量开始时间的差异。如果两个程序的大小相当,则差异可归因于动态加载。

  1. Exec(myprogram)
  2. Operating system loads myprogramm into memory
  3. Operating system turns over execution to _start
  4. Some initialization happens and the runtime linker is run
  5. main() is called

Assuming that the above list is correct and I did not leave out any major steps

实际上这不是正确的列表,至少 Linux 是这样。 主要说明:动态链接器(将所需库映射到的程序 程序获得控制权之前的进程地址 space 运行。有一个带有动态链接器路径的 ELF 部分,通常类似于 /lib/ld-linux.so.2,并且该程序在实际程序之前获得控制权,并且它 "load" 共享库。

小注:"load into memory" 实际上不是真的,实际上带有可执行文件和共享库文件的文件映射到进程的地址 space,code/data 的下一个 4K 是按需加载的(4K 是普通内存页面大小)。将控制权交给 _start 也不完全正确,传递执行控制权的点取自 ELF header,惯例是“_start”符号指向该地址,但我想您可以创建无需工作的 ELF 文件“_start”符号。

I would be interested in two things:

  1. What is the overhead of step 4. according to theory?
  2. How can I determine in practice the overhead of step 4. (e.g. for real programs such as Firefox or Chrome)?

如我所写,在第 4 步动态链接器不是 运行,实际上,如果您检测 firefox 或 chrome 程序,则无法测量 ld-linux.so 的工作时间。 2,因为它 运行 在 firefox/chrome 可执行文件的任何指令取得控制权之前 运行。

您可以编辑 firefox 的可执行文件并将 /lib/ld-linux.so.2 替换为 /lib/ld-linux.so.3,然后破解 glibc (ld-linux.so 的一部分)并对其进行测量以测量时间。

main 之前 运行 的所有其他代码,我认为您可以按正常方式分析, 例如像这样:What is a good easy to use profiler for C++ on Linux?

Assuming that the above list is correct

正如 this answer 所解释的那样,它并不完全正确。

What is the overhead of step 4. according to theory?

取决于并且变化很大。

一些起作用的因素:

  1. 程序link针对多少个动态库?

    加载几十个库的情况并不少见。
    当有 5000 个或更多时 dramatically slow down

  2. 程序引用了多少数据和函数符号?

    数据引用必须在加载时解析,但函数符号可以延迟解析。 (更多关于惰性符号解析 here。)

How can I determine in practice the overhead of step 4. (e.g. for real programs such as Firefox or Chrome)?

使用 GLIBC,只需在环境中设置 LD_DEBUG=statistics。例如:

LD_DEBUG=statistics /bin/date
    104984:
    104984: runtime linker statistics:
    104984:   total startup time in dynamic loader: 1348294 clock cycles
    104984:         time needed for relocation: 501668 clock cycles (37.2%)
    104984:                  number of relocations: 90
    104984:       number of relocations from cache: 3
    104984:         number of relative relocations: 1201
    104984:        time needed to load objects: 413792 clock cycles (30.6%)
Sun Dec 11 17:51:35 PST 2016