1 介绍

OProfile是Linux上的性能监测工具,通过CPU硬件提供的性能计数器对事件(如CPU Cycle、Cache Miss等)进行采样,可以帮助开发者从代码层面分析程序的性能消耗情况,很方便的找出影响程序性能的问题点。

硬件上,OProfile支持多种架构的CPU,包括Alpha、MIPS、ARM、x86/x86-64、Sparc64、PowerPC等;软件上,OProfile支持Linux 2.2、2.4、2.6多种版本的内核,并且所占用的系统开销较小,一般在1%-8%范围内,具体数值依赖于采样频率。

2 环境准备

2.1 编译Linux内核

OProfile需要Linux内核的支持,到Linux内核源代码目录中(一般为/usr/src/linux-xxx)执行下面的命令:

make menuconfig

在配置菜单中找到OProfile的选项并将其打开,然后保存退出。

如果不想用make menuconfig的方式配置,可以直接修改.config文件,把下面两个选项打开即可:

CONFIG_PROFILING=y
CONFIG_OPROFILE=y

配置完成后,依次执行下面的命令编译内核:

make dep
make clean
make bzImage

编译完成后,会生成内核文件bzImage,将其拷贝到/boot目录中(最好重命名一下,和其它内核文件区别开,比如vmlinuz-linux-with-oprofile)。最后将启动菜单中的内核引导文件换成新的内核,完成后重新启动Linux。

2.2 安装OProfile

如果所使用的Linux发行版中已经自带了软件管理器,则直接在相应的管理器中搜索并安装oprofile即可。

如果没有软件管理器,或是在管理器中没有搜到OProfile,可以直接访问OProfile官网下载一个,下载解压后执行标准的三步曲完成安装:

./configure
make
make install

3 性能监测

3.1 设置监测参数

这里只列出了最常用的设置,详细参数可以通过opcontrol --help查看。

1) 设置监测事件

opcontrol --event=eventspec

其中eventspec即为需要监测的事件,参数格式为name:count:unitmask:kernel:user,各字段含义如下:

  • name - 事件名称,如CPU_CLK_UNHALTED,支持的事件列表可以通过opcontrol --list-events命令列出
  • count - 触发一次采样的事件计数值,如100000
  • unitmask - 硬件单元掩码,如0x0F
  • kernel - 是否对内核进行监测,取值0或1
  • user - 是否对用户空间进行监测,取值0或1

2) 设置内核文件

当要对内核进行监测时,需要指定内核的文件路径,使用下面的命令(其中file为内核文件完整路径):

opcontrol --vmlinux=file

如果不需要对内核进行性能监测,则此处直接使用--no-vmlinux参数:

opcontrol --no-vmlinux

3) 设置监测数据分离方式

opcontrol --separate=type[,types]

其中,type的参数选项如下:

  • none - 不分离监测数据
  • library - 为每个应用程序分离出动态库的监测数据
  • kernel - 在library的基础上分离内核的监测数据
  • thread - 分离出每个线程的监测数据
  • cpu - 分离出每个CPU的监测数据
  • all - 分离以上全部监测数据

4) 设置待监测的二进制文件名称

可以设置多个(逗号分隔),如果不设置则默认为全部:

opcontrol --image=name[,names]

3.2 开始监测

opcontrol --init
opcontrol --start

3.3 执行待监测的业务功能

监测器已开始运行,现在可以执行相应程序的业务功能了。

3.4 保存监测数据

程序业务执行完成后,使用下面的命令将监测数据转储下来:

opcontrol --dump

3.5 停止监测

opcontrol --stop
opcontrol --shutdown
opcontrol --deinit

4 性能数据分析

监测数据转储后,即可以通过opreportopannotate命令查看性能消耗情况了。

1) 显示各模块整体的性能消耗情况

opreport

2) 显示各函数的性能消耗情况

opreport -l

3) 显示代码级别的性能消息情况

opannotate -s

:该功能需要在用户程序编译的时候加上调试选项(gcc编译器需要加上-g选项)。

4) 显示反汇编代码的性能消耗情况

opannotate -a

5 实例

下面举一个利用OProfile对程序性能进行优化的例子。

5.1 优化前性能监测

首先看一下下面这段代码,其中reverser函数的功能是将输入的数据以字节为单位做逆序处理。

#include <stdio.h>
#include <string.h>

#define OK          (0)
#define ERR         (1)

#define BUF_SIZE    (0x100)

/* 交换两个变量的值 */
#define SWAP(a, b)  \
    do { \
        char x; \
        x = (a); \
        (a) = (b); \
        (b) = x; \
    } while (0)

/* 将数据逆序处理 */
int reverser(char *data, unsigned int len)
{
    int i;

    if (NULL == data)
    {
        return ERR;
    }

    for (i = 0; i < len / 2; ++i)
    {
        SWAP(*(data + i), *(data + len - i - 1));
    }

    return OK;
}

/* 主函数 */
int main(int argc, char *argv[])
{
    char str[BUF_SIZE] = {0};
    int ret = OK;
    int i = 0;

    strcpy(str, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ");

    printf("original string : %s \r\n", str);

    for (i = 0; i < 0x800001; ++i)
    {
        ret = reverser(str, strlen(str));
    }

    printf("reversely string : %s \r\n", str);

    return 0;
}

源文件下载:链接

然后对该代码进行编译并生成可执行文件,注意编译的时候加上-g选项,这样会生成带调试信息的程序,后面显示监测报告的时候会用到:

gcc reverser.c -g -o reverser

接下来对OProfile的参数进行设置:

opcontrol --event=CPU_CLK_UNHALTED:100000:0:0:1
opcontrol --no-vmlinux
opcontrol --image=reverser

启动OProfile监测守护进程,同时将之前的监测数据清空:

opcontrol --init
opcontrol --start
opcontrol --reset

运行待监测程序:

Run the Reverser

转储OProfile监测数据:

opcontrol --dump

停止OProfile监测守护进程:

opcontrol --stop
opcontrol --shutdown
opcontrol --deinit

执行opreport命令,查看监测统计数据:

Result of OPReport for Reverser

执行opannotate -s命令,查看详细的监测统计数据(代码级):

Result of OPAnnotate for Reverser

5.2 性能优化

从上面的报告中可以看到,reverser函数共触发了近6万5千次采样,其中for (i = 0; i < len / 2; ++i)这一行占用了1万6千多次,消耗了约25%的性能,而实际可以发现len / 2这个操作不需要每次循环都要计算一次,完全可以把它提取到循环外部,于是这段代码可以改成这个样子:

int reverser(char *data, unsigned int len)
{
    int i;
    int cnt;

    if (NULL == data)
    {
        return ERR;
    }

    /* 优化: 将循环总数放到循环体外 这样只进行一次计算即可 */
    cnt = len / 2;
    for (i = 0; i < cnt; ++i)
    {
        SWAP(*(data + i), *(data + len - i - 1));
    }

    return OK;
}

源文件下载:链接

5.3 优化后性能监测

代码优化后重复上面的监测动作,检查优化后的性能监测数据。

执行opreport命令:

Result of OPReport for Reverser (Optimize)

执行opannotate -s命令:

Result of OPAnnotate for Reverser (Optimize)

5.4 性能优化对比

从优化前后的报告中可以对比出,reverser函数从6万5千次采样降低到了4万8千次左右,总体性能提升了约25%,其中for循环条件的优化直接贡献了15%左右的性能提升,另外循环条件的改变也影响了编译器对代码编译时的流程优化,这也间接带来了10%左右的性能提升。

6 后记

本文讲述了OProfile的基本用法,以最常见的CPU Cycle作为例子演示了性能优化的过程。事实上,OProfile还可以用到的统计计数事件还有很多(具体依赖CPU硬件的支持),像Cache Miss、TLB Miss等等,这些在性能优化中都会涉及到,这需要开发者在实际环境中不断的操作和体会,从而逐渐学习并掌握性能优化中的各种技巧。


Comments