码迷,mamicode.com
首页 > 其他好文 > 详细

从系统的角度分析影响程序执行性能的因素——动态链接性能

时间:2021-05-24 14:18:04      阅读:0      评论:0      收藏:0      [点我收藏+]

标签:print   har   一起   高级   oid   com   计算机   使用命令   关系   

Linux系统

在linux内核中,比较重要的部分包括系统调用、进程管理、内存管理和文件系统。其中,系统调用就是非内核软件想要使用高级内核功能的一种方法,进程管理是不同任务之间的来回切换,文件系统是硬件资源或者数据资源的一种方式,内存管理是对内存的分配和回收方式。
本文将简要讲解系统调用、进程管理和文件系统的知识,并且分析动态链接对程序性能的影响。

系统调用

CPU将指令等级分级,目的是为了将系统的核心不能与外部的进程隔离开,保护系统不被糟糕的程序搞得崩溃。也就是说有些指令是需要在高的权限等级才能运行的,而用户程序需要使用这些指令就需要用得到系统调用,本质是把需要执行的指令告诉内核,让内核中的特定代码段来执行这些指令,这些特定的代码段是已经写好了的安全的代码段,所以这种方法是安全的。
内核执行完特定的代码段之后在返回到原来的用户进程。
当然这里还要涉及到,中断的概念。用户进程就是通过触发中断来告诉内核需要进行的代码段。

进程管理

现代的操作系统大多的面向多任务的。也就是说同一时间内,计算机中会存在多个进程,那么需要怎么管理这些进程呢?
首先我们需要一个数据结构来描述进程的信息,于是我们有了进程描述符。进程描述符中记录了进程运行过程中寄存器内的信息、使用到的资源、状态等信息
然后进程存储在哪里呢?
我们就是用一个双向队列来存储进程吧,这里也要记录进程之间的关系。
进程很多的时候什么进程先运行呢?
这就引入了进程的优先级和调度策略。优先级有0-139,优先级数值越低的优先级越高,Linux内核中有4中常用的调度策略SCHED_NORMAL、SCHED_FIFO 、SCHED_RR和SCHED_BATCH,不同的优先级使用不同的调度策略
当然进程的切换时候的选择是非常复杂的。

文件系统

计算机中的文件多种多样,存储文件的介质和存储方式多种多样,我们能不能把这些多样的文件统一起来,屏蔽其中的多样性让用户编程简单化。这就是文件系统得目的。
文件系统将文件抽象成文件描述符,对文件得读写操作都可以通过文件描述符实现,即使文件的类型不一样。

进程的加载

进程在没有加载进内存执行的时候称为程序,是存储在外存的一个可执行文件。可执行文件是一种ELF文件。

可执行文件

在C语言的世界里,编辑好的代码是源文件(单纯的文本文件)。源文件变成一个可执行文件需要经过以下几个步骤:

  1. 预处理
  2. 编译
  3. 汇编
  4. 链接
    其中链接完后就生成了可执行文件,其中汇编完后生成的文件是可重定位的ELF文件

链接

两个概念:

  • ELF文件中存在多个section,存储着文件的信息,如代码,数据等
  • 经过汇编后的源文件会存在一些尚未被识别的符号,例如不在源文件中的函数,这些函数没有真正的地址,在没有找到函数之前是无法进行调用的。
  • 这些没有识别的符号就存储在ELF文件中
  • 链接的实质就是多个ELF文件section的拼接

静态链接

链接的时候直接将需要的代码拼接带最终的可执行文件中。生成的可执行文件ELF内部不存在找不到的函数。

动态链接

编译时不直接复制可执行代码,而是通过记录一系列符号和参数,在程序运行或加载时将这些信息传递给操作系统。
操作系统将需要的动态库加载到内存中, 然后程序在运行到指定的代码时, 去共享执行内存中已经加载的动态库。
动态链接分为:

  • 装载时链接,就是程序载入到内存的时候将动态库一起载入
  • 运行时链接,程序载入内存时不是马上载入,而是需要运行某个函数是才载入
    两种动态链接方式的主要区别在于共享库载入内存的时机不同,其中运行时链接会更加直观。运行时动态链接会使用dlopen()函数来加载共享库,就像加载文件一样,然后通过dlsym()函数和符号来找到需要的函数。
    转载时链接的加载就是通过系统调用加载,在使用系统调用exec加载可执行文件的并返回用户态前会执行ELF中.interp section中的代码,然后启动动态链接器ld通过遍历依赖树的方式加载动态链接库。

静态链接和动态链接的性能比较

首先准备一个testlib.c和testlib.h

// testlib.c
#include "testlib.h"

int add(int a,int b) {
    return a + b;
}
//testlib.h
#ifndef _DL_LIB_EXAMPLE_H_
#define _DL_LIB_EXAMPLE_H_

#ifdef __cplusplus
extern "C" {
#endif

int add(int a,int b);

#ifdef __cplusplus
}
#endif
#endif /* _DL_LIB_EXAMPLE_H_ */

使用命令gcc -shared testlib.c -o libtestlib.so将编译出动态链接库

静态链接

使用程序teststatic.c如下:

#include <stdio.h>
#include "testlib.h"

int main() {
    long a = 0,i = 0;
    for (i = 0;i < 1e10;i++) {
        a += add(i,-i + 100);
    }
    printf("%ld\n",a);
    return 0;
}

使用命令gcc teststatic.c testlib.c -o teststatic -static编译出静态链接的可执行文件
然后使用time ./teststatic查看程序的运行时间
技术图片

转载时链接

这部分的代码与静态链接的相同,但是编译的命令不同。
源文件名为testloadlink.c
使用命令gcc testloadlink.c -o testloadlink -L"/home/liuyong/linux_study/final" -l"testlib"编译。
然后使用命令export LD_LIBRARY_PATH=$PWD将当前目录添加到动态链接库可查找的目录中。
使用time ./testloadlink查看程序运行时间
技术图片

运行时链接

源文件代码

#include <stdio.h>
#include <dlfcn.h>

int main() {
    void * handle = dlopen("testlib.so",RTLD_NOW);
    int (*add)(int,int);
    add = dlsym(handle,"add");
    long a = 0,i = 0;
    for (i = 0;i < 1e10;i++) {
        a += add(i,-i + 100);
    }
    printf("%ld\n",a);
    return 0;
}

使用命令gcc testruntimelink.c -o testruntimelink -L"/home/liuyong/linux_study/final" -l"testlib" -ldl编译
然后运行得到用时
技术图片

结果

通过多次的运行测试,可以发现静态链接的运行时间普遍在20s左右,而装载时链接会在22s左右,运行时链接在21s左右。静态链接比动态链接速度快。
但是静态链接的可执行文件大小比动态链接大很多
技术图片

原因

一开始以为静态链接会导致内联优化,但是开始-o2优化级别后静态链接的时间还是20s左右。所以还是按照常规的解释
静态链接不需要在内存中查找共享库的位置,代码跳转在程序自己的段空间中速度会快很多
而动态链接在运行到需要链接的函数时会去查找内存中的动态库,速度就慢了

从系统的角度分析影响程序执行性能的因素——动态链接性能

标签:print   har   一起   高级   oid   com   计算机   使用命令   关系   

原文地址:https://www.cnblogs.com/ly-yong/p/14778379.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!