一、PAPI简介
PAPI是田纳西大学创新计算实验室开发的一组可以在多个处理器平台上对硬件性能计数器进行访问的标准接口,它的目标是方便用户在程序运行时监测和采集由硬件性能计数器记录的处理器事件信息。用户可以使用其提供的high/low api对程序某一段的使用时钟周期数,执行指令数,L1/L2 cache miss/access数,TLB miss数等等都统计出来,使用户能够直观的了解到程序的局部性如何。
不同的处理器会根据自身的体系结构特征定义出不同的处理器事件集合,在 PAPI 中这些事件被称为原生事件(Native Event)。同时,不同的处理器也会具有不同数量的硬件性能计数器,而在任意时刻一个计数器只能对一个指定的原生事件进行监测。考虑到事件监测和性能分析的需求,不同处理器的原生事件集合往往在功能上会有交集(例如那些和存储层次访问、Cache 一致性协议、周期和指令计数、功能单元和流水线状态等方面相关的事件),但是其对应的原生事件名称却未必相同。为了便于事件甄别,PAPI 将这些在不同处理器中存在功能共性的原生事件抽象成了 PAPI 接口专用的预制事件(Preset Event)并统一命名,所以具有一定的可移植性。
1、支持的性能计数器事件:
PAPI 支持一百多个事件。其标准事件分为 4 类:存储层次访问事件;周期与指令计数;功能部件与流水线状态事件; Cache 一致性事件,与 SMP 系统的 cache 一致性协议相关。
PAPI 包含一个工具程序papi_avail,,可以检测用户平台具有哪些事件,如:
此外还有papi_mem_info和papi_cost等命令,用来查看TLB,Cache信息和 查看papi开销等。
2、PAPI提供了两个向底层硬件的接口:
高层接口:比较简单,用于完成基本的计数测量(提供启动、停止和读取特定事件的能力)
PAPI 高层接口提供了一些访问硬件性能计数器所需的基本功能,例如配置计数器、启动计数、停止计数、读取计数器的数值等。高层接口只能利用 PAPI 预制事件,而不能够通过配置计数器去监测超出预制事件覆盖范围以外的处理器原生事件。不过,PAPI 高层接口能够直接返回在程序测评中最经常使用的一些性能指标,例如每个周期执行完成的指令数、每秒执行完成的浮点指令 / 浮点操作数、程序的运行时间等;另外,高层接口还能获取一些系统信息,例如处理器能够支持的硬件性能计数器的个数等。
底层接口:管理用户定义的事件组(称为 EventSet)中的事件,完全可编程,线程安全,为工具开发人员和高级用户提供方便。
高层接口获得的数据很多是未经过加工的原始数据,从中不能直接看出应用程序的存储层次与流水线利用情况的好坏。不同于高层接口只能使用 PAPI 预制事件,PAPI 底层接口能够直接使用原生事件对程序运行时的处理器硬件行为进行监测。用户可以将一个或多个原生事件组成一个事件组(Event Set),然后通过设置硬件性能计数器对事件组中所有的原生事件同时进行监测,进而根据监测结果分析程序的性能问题,例如通过同时采集每秒执行完成的浮点 指令数和 L1 Cache 失次数就有助于分析是否是因为 L1 Cache 的命中率不高导致了程序浮点性能的下降。需要注意的是,事件组中的原生事件个数不能够超过处理器所能支持的硬件性能计数器个数。
list . 常用的 PAPI 底层接口(C语言)
PAPI_library_init
初始化 PAPI 接口库
PAPI_create_eventset
创建事件组
PAPI_add_event / PAPI_add_events
向事件组中添加原生事件或者 PAPI 预制事件
PAPI_remove_event PAPI_remove_events
从事件组中删除事件
PAPI_start
启动计数器对事件组的计数
PAPI_read
读取计数器数值
PAPI_stop
停止计数器计数并读取当前的计数器数值
PAPI_cleanup_eventset
清除事件组中的事件
PAPI_destroy_eventset
销毁事件组
PAPI_shutdown
终止使用 PAPI 并释放所有相关资源
//具体使用见后面实例2
PAPI是一组可以在多个处理器平台上对硬件性能计数器进行访问的本地接口,所以对于不同的处理器,安装方法也不同,部分AMD的CPU和INTEL的奔腾系列,至强系列都需要打补丁。补丁使用perfctr-2.6.x,对内核打补丁,重新编译的步骤比较繁琐,安装步骤在PAPI压缩包的Install.txt文件里有说明。
下面是对于一般的处理器(INTEL的i系列等)的安装步骤。
二、PAPI安装方法
在Ubuntu14.10上安装:
1、到PAPI官网下载PAPI 5.4.1压缩包。http://icl.cs.utk.edu/papi/software/index.html
2、将压缩包拷贝至/usr/local/下并解压。
3、测试PAPI
进入src目录并依次执行以下指令。
% ./configure % make % make test
如果出现PASSED则是通过测试,可以安装。
% make fulltest (相当于./run_tests.sh,简单测试,可忽略) % ./run_tests.sh -v(详细测试)
如果提示没有权限就使用chmod命令更改权限即可。
这个过程比较缓慢,下面是部分过程截图。
4、安装PAPI
a) To install PAPI libraries and header files from the build tree:
% make install
b) To install PAPI manual pages from the build tree:
% make install-man
c) To install PAPI test programs from the build tree:
% make install-tests
d) To install all of the above in one step from the build tree:
% make install-all
e) To create a binary kit, papi-<arch>.tgz:
% make dist
papi默认是安装在 /usr/local 下的,
papi可执行命令安装在bin下
papi的头文件放在include下
papi的库文件放在lib下,包括静态库libpapi.a,动态库libpapi.so.5.4.1.0
还有share、man目录下也安装了一些东西
5、由于papi的相关库安装在 /usr/local/lib 下,而我们编译程序链接动态或者静态库时,默认不会再 /usr/local/lib 下查找相关库的,所以为了在编译程序时可以自动链接/usr/local/lib下的papi相关库(例如: gcc ** -lpapi),我们做如下操作:
将 /usr/local/lib 加入文件 /etc/ld.so.conf 的最后一行,然后执行 ldconfig 命令,在编译时需要加入编译选项 -lpapi,使得程序可以动态链接到相应的函数实现。
6、编写一个C程序,调用 papi 相关函数,使用命令 gcc *.c -lpapi 命令编译,查看是否能够正常使用 PAPI .
三、使用PAPI
使用PAPI需要在源码进行插桩, 但是非常简单,一共5个阶段
初始化papi
/* Initialize the PAPI library */ if (PAPI_library_init(PAPI_VER_CURRENT) != PAPI_VER_CURRENT) exit(1); /*Initialize the papi thread support:初始化线程支持,不使用线程这个部分可以省略。*/ if (PAPI_thread_init(pthread_self) != PAPI_OK) exit(1);
创建事件集
事件就是指的是你要测试哪些性能数据,可以选择的性能指标通过papi_avail可以查看。例如L1,L2,L3 cache miss,clock cycle等等。
/* Create an EventSet */ int EventSet = PAPI_NULL; int retval = PAPI_create_eventset (&EventSet); assert(retval==PAPI_OK); /* Add an event*/ retval = PAPI_add_event(EventSet, PAPI_L3_TCM); assert(retval==PAPI_OK);
有些Event是支持一起测试的,如果想一次测试多个数据,再增加一个Event,至于哪些可以一起测试,需要自我探索一下。有一些可以一起测,有些是不可以的。
/*Add another event*/ retval = PAPI_add_event(EventSet, PAPI_TOT_INS); assert(retval==PAPI_OK); 开始计数 /* Start counting events */ if (PAPI_start(EventSet) != PAPI_OK) retval = PAPI_start (EventSet); assert(retval==PAPI_OK);
读取数据
long long values1[2]; long long values2[2]; PAPI_read(EventSet, values1); assert(retval==PAPI_OK); //Do something /* Stop counting events */ retval = PAPI_stop (EventSet,values2); assert(retval==PAPI_OK);
得到结果
L3_TCM: values2[0] – values1[0] L3 cache miss
TOT_INS: values2[1] – values1[1] total instruction counts
释放事件,清理工作
/* Clean up EventSet */ if (PAPI_cleanup_eventset(EventSet) != PAPI_OK) exit(-1); /* Destroy the EventSet */ if (PAPI_destroy_eventset(&EventSet) != PAPI_OK) exit(-1); /* Shutdown PAPI */ PAPI_shutdown();
四、实例测试
实例一
http://icl.cs.utk.edu/projects/papi/wiki/PAPITopics:Getting_Started 官网的例子
我的电脑输出的结果为:
实例二
1 #include "papi.h" 2 3 #include <stdlib.h> 4 5 #include <stdio.h> 6 7 int main() { 8 9 int EventSet; 10 11 int i, sum; 12 13 long_long values[2], values1[2], values2[2]; 14 15 if (PAPI_library_init(PAPI_VER_CURRENT) != PAPI_VER_CURRENT) 16 17 exit(-1); 18 19 EventSet = PAPI_NULL; 20 21 if (PAPI_create_eventset(&EventSet) != PAPI_OK) 22 23 exit(-1); 24 25 if (PAPI_add_event(EventSet, PAPI_TOT_INS) != PAPI_OK) 26 27 exit(-1); 28 29 if (PAPI_add_event(EventSet, PAPI_L1_DCM) != PAPI_OK) 30 31 exit(-1); 32 33 if (PAPI_start(EventSet) != PAPI_OK) 34 35 exit(-1); 36 37 if (PAPI_read(EventSet, values1) != PAPI_OK) 38 39 exit(-1); 40 41 for (i=0;i<10000;i++) 42 43 sum+=i; 44 45 if (PAPI_stop(EventSet, values2) != PAPI_OK) 46 47 exit(-1); 48 49 if (PAPI_cleanup_eventset(EventSet) != PAPI_OK) 50 51 exit(-1); 52 53 if (PAPI_destroy_eventset(&EventSet) != PAPI_OK) 54 55 exit(-1); 56 57 PAPI_shutdown(); 58 59 /* Get value */ 60 61 values[0]=values2[0]-values1[0]; 62 63 values[1]=values2[1]-values1[1]; 64 65 printf("TOT_INS:%lld\nL1_DCM: %lld\n",values[0], values[1]); 66 67 return 0; 68 69 }
我的电脑输出的结果为:
TOT_INS:50919
L1_DCM:14
实例三
1 #include "papi.h" 2 3 #include <stdlib.h> 4 5 #include <stdio.h> 6 7 int main() { 8 9 int EventSet; 10 11 int i, sum; 12 13 long_long values[1], values1[1], values2[1]; 14 15 /* Initialize the PAPI library */ 16 17 if (PAPI_library_init(PAPI_VER_CURRENT) != PAPI_VER_CURRENT) 18 19 exit(-1); 20 21 // exit(-1); 22 23 /* Create an EventSet */ 24 25 EventSet = PAPI_NULL; 26 27 if (PAPI_create_eventset(&EventSet) != PAPI_OK) 28 29 exit(-1); 30 31 /* Add an event about Total Instructions Executed (PAPI_TOT_INS) to EventSet */ 32 33 if (PAPI_add_event(EventSet, PAPI_TOT_INS) != PAPI_OK) 34 35 exit(-1); 36 37 /* Start counting events */ 38 39 if (PAPI_start(EventSet) != PAPI_OK) 40 41 exit(-1); 42 43 /* Read counters before workload running*/ 44 45 if (PAPI_read(EventSet, values1) != PAPI_OK) 46 47 exit(-1); 48 49 /* Do some computation here */ 50 51 for (i=0;i<10000;i++) 52 53 sum+=i; 54 55 /* Stop counting events */ 56 57 /* Stop counting events */ 58 59 if (PAPI_stop(EventSet, values2) != PAPI_OK) 60 61 exit(-1); 62 63 /* Clean up EventSet */ 64 65 if (PAPI_cleanup_eventset(EventSet) != PAPI_OK) 66 67 exit(-1); 68 69 /* Destroy the EventSet */ 70 71 if (PAPI_destroy_eventset(&EventSet) != PAPI_OK) 72 73 exit(-1); 74 75 /* Shutdown PAPI */ 76 77 PAPI_shutdown(); 78 79 /* Get value */ 80 81 // values[0] = values2[0] – values1[0]; 82 83 values[0]=values2[0]-values1[0]; 84 85 printf("%lld\n",values[0]); 86 87 return 0; 88 89 }
我的电脑输出的结果为50891
实例四
#include "papi.h" #include<stdlib.h> #include<stdio.h> int a[1000][1000]; int c[1000][1000]; int main() { int EventSet = PAPI_NULL; int retval; long_long values[1]; /* Initialize the PAPI library */ retval = PAPI_library_init(PAPI_VER_CURRENT); if (retval != PAPI_VER_CURRENT) { fprintf(stderr, "PAPI library init error!\n"); exit(1); } /* Create an EventSet */ if (PAPI_create_eventset(&EventSet) != PAPI_OK) { printf("create error\n"); exit(1); } /* Add Total Instructions Executed to our EventSet */ if (PAPI_add_event(EventSet, PAPI_L1_DCM) != PAPI_OK) { printf("add error\n"); exit(1); } if (PAPI_start(EventSet) != PAPI_OK) { printf("start error\n"); exit(1); } if (PAPI_read(EventSet, values) != PAPI_OK) { printf("read error\n"); exit(1); } int m,n; for(m=0;m<1000;m++) { for(n=0;n<1000;n++) { a[n][m]=a[n][m]+m+n; } } /* Do some computation here */ if (PAPI_read(EventSet, values) != PAPI_OK) { printf("read error\n"); exit(1); } long_long b=values[0]; if(PAPI_reset(EventSet)!=PAPI_OK) { printf("reset error\n"); exit(1); } for(m=0;m<1000;m++) { for(n=0;n<1000;n++) { c[m][n]=c[m][n]+m+n; } } /* Do some computation here */ if (PAPI_stop(EventSet, values) != PAPI_OK) { printf("stop error\n"); exit(1); } if (PAPI_remove_event(EventSet, PAPI_L1_DCM) != PAPI_OK) { printf("stop error\n"); exit(1); } if (PAPI_destroy_eventset(&EventSet) != PAPI_OK) { printf("stop error\n"); exit(1); } PAPI_shutdown(); printf("%lld\n",b); printf("%lld",values[0]); getchar(); return 0; }
我的电脑输出的结果为:
1364841
34786
最后推荐 http://www.ibm.com/developerworks/cn/java/j-lo-papi/ 这篇文章写得很好。