Computer Performance Microscopy with SHIM

文章主要是关于细粒度高频率观测程序性能,作者yangxi,文章和实验代码均可以在github上找到 。一般的程序性能观测采样是采用的linux的perf模块,但是perf只能观测较为宏观的程序性能参数,如果进行高频度的观测,观测程序就会对被观测程序进行影响,而这篇文章中的shim正是为解决这个问题而存在的。而shim之所以能在elfen论文中被使用也正是因为其高频度的准确观测程序数据,这一点使得其能够高频度观测到cpu空闲的区间,进而被elfen所用。

实验代码主要包括两个部分,一个是一个linux内核模块ppid_module,另一个是shim的核心代码。文章中的实验数据是对一个java模拟器进行性能观测,这里我仅对ppid_module和shim的两个简单使用例子进行说明。

ppid 内核模块

这个模块的主要作用是任何时候显示哪个线程跑在哪个cpu上,主要方式是允许一个用户程序映射一段无cache的连续物理内存缓冲区,ppid主要的工作在初始化和映射,以及一个额外的在内核中schedule处被调用的一个回调函数

回调函数

回调函数主要就是在内核的schedule函数结尾处加了个指针,指向我们将在初始化时指定的函数,该函数主要功能为获取下一个要执行的线程的id及cpu的编号,并一起存入初始化时分配的内存中。因为要对schedule函数进行改动,所以利用如下补丁对linux源码进行改动,改动完后需要重新编译内核源码安装,并选择对应内核重新启动

diff --git a/kernel/sched/core.c b/kernel/sched/core.c
index 649c9f8..e5a02fd 100644
--- a/kernel/sched/core.c
+++ b/kernel/sched/core.c
@@ -3365,6 +3365,9 @@ pick_next_task(struct rq *rq)
 	BUG(); /* the idle class will always have a runnable task */
 }

+void (*switchCallBack)(struct task_struct *prev,struct task_struct *next) = NULL;
+EXPORT_SYMBOL(switchCallBack);
+
 /*
 * __schedule() is the main scheduler function.
 */
@@ -3445,6 +3448,9 @@ need_resched:
 	sched_preempt_enable_no_resched();
 	if (need_resched())
 		goto need_resched;
+
+	if (switchCallBack)
+	 switchCallBack(prev,next);
 }

 static inline void sched_submit_work(struct task_struct *tsk)

模块初始化 ppid_map_init

ppid模块初始化的过程中,主要利用了 dma_alloc_coherent 函数分配了一段无cache的连续内存,dma_alloc_coherent这个函数本来是为DMA设备进行内存分配的,在被调用时实际上是会返回两个值

  • 函数的返回值,void *类型,代表缓冲区的内核虚拟地址
  • dma_handle保存相关的总线地址,即物理地址,为DMA设备所用

然后是需要创建一个字符设备文件/dev/ppid_map,在初始化过程中这个字符设备会加如内核中,并在之后的映射过程与分配的无cache内存进行关联。 手动创建shell脚本如下

#!/bin/bash
if [ -a /dev/ppid_map ]
 then
  echo "/dev/ppid_map is exist already."
  exit
fi

deviceID=`cat /proc/devices | grep ppid | grep -o [0-9]*`
echo $deviceID
mknod /dev/ppid_map c $deviceID 0
ls -lh /dev/ppid_map

文件操作映射过程 .mmap = mmap_mmap

映射过程主要利用dma_mmap_coherent函数将分配的dma无cache内存区域与用户进程的虚拟内存块vma进行挂钩,使得用户进程能对分配的内存进行访问

example

如下为一个ppid模块的测试程序

/**
 * ppid_test.c
 */
#include < stdio.h>
#include < unistd.h>
#include < sys/mman.h>
#include < sys/types.h>
#include < sys/stat.h>
#include < fcntl.h>
#include < stdlib.h>

#define NPAGES 1

int main(void)
{
	int fd;
 int *kadr;
	int len = NPAGES * getpagesize();

	if ((fd=open("/dev/ppid_map", O_RDWR|O_SYNC)) <  0) {
		perror("open");
		exit(-1);
	}
	fprintf(stderr, "/dev/ppid_map: open OK\n");

	kadr = mmap(0, len, PROT_READ|PROT_WRITE, MAP_SHARED| MAP_LOCKED, fd, 0);

	if (kadr == MAP_FAILED)	{
		perror("Could not mmap /dev/ppid_map");
		exit(-1);
	}
	fprintf(stderr, "ppid_map: mmap OK\n");

	int cpu = 0;
	while(1){
	 for (cpu=0; cpu< 8; cpu++){
	  int *buf = kadr + cpu * (64/sizeof(int));
	  printf("CPU%d: tgid %d pid %d\n", cpu, buf[0], buf[1]);
	 }
	 sleep(1);
	}
	close(fd);
	return(0);
}

下面,为输出结果,由于测试平台为intel 双核四线程cpu,故只输出了cpu0到cpu3的结果,结果为线程与cpu号的一一对应

/dev/ppid_map: open OK
ppid_map: mmap OK
CPU0: tgid 238 pid 238
CPU1: tgid 5774 pid 5774
CPU2: tgid 0 pid 0
CPU3: tgid 0 pid 0
CPU4: tgid -1 pid -1
CPU5: tgid -1 pid -1
CPU6: tgid -1 pid -1
CPU7: tgid -1 pid -1
CPU0: tgid 7 pid 7
CPU1: tgid 5774 pid 5774
CPU2: tgid 1878 pid 1878
CPU3: tgid 0 pid 0
CPU4: tgid -1 pid -1
CPU5: tgid -1 pid -1
CPU6: tgid -1 pid -1
CPU7: tgid -1 pid -1
...

shim 核心代码

主要数据结构如下

struct shim_hardware_event {
 int index;
 int fd;
 struct perf_event_attr perf_attr;
 struct perf_event_mmap_page *buf;
 char * name;
};

typedef struct shim_worker_struct shim;

struct shim_worker_struct{
 int cpuid;
 int nr_hw_events;
 struct shim_hardware_event *hw_events;
 int (*probe_other_events)(uint64_t *buf, shim *myshim);
 int (*probe_tags)(uint64_t *buf, shim * myshim);
};

作者在shim中提供了一些基本的函数,如relax_cpu释放cpu控制权,作用相当于机器指令中的pause, 另外rdtsc用来获取当前的周期数,get_cpuid用来获取cpuid。主要部分还是对于perfmon库提供的函数进行的封装。

shim初始化

利用perf库的 pfm_initialize()函数来查询有效PMU事件进行激活

创建数据结构,对输入的不同名称的事件进行相应的创建及初始化工作

数据读取及可信赖度

在函数shim_read_counters中shim两次调用rdtsc进行测量当前的周期数,并在两次读取中间进行各种事件的测量工作,若前后两次read_counters的end之差值和begin之差值在可信赖范围内相同,则可认为shim对测量没有影响。

example

利用gcc *.c -o shim -lpthread -lpfm对如下示例程序进行编译执行,可进行测试

/*
	shim_example.c
*/
#include "shim.h"
#define MAX_HW_COUNTERS (10)
#define INDEX_HW_COUNTERS (2)
int
main(int argc, char **argv)
{
 int i;
 int trustable;

 shim_init();
 shim * my = (shim *)calloc(1, sizeof(shim));
 shim_thread_init(my, 0, argc-1, argv+1);

 uint64_t begin[MAX_HW_COUNTERS];
 uint64_t end[MAX_HW_COUNTERS];

 shim_read_counters(begin, my);
 printf("hello world\n");
 shim_read_counters(end, my);

 trustable = shim_trustable_sample(begin, end, 99, 101);

 printf("Trustable samples:%d\n", trustable);
 for (i=0; i< my->nr_hw_events; i++){
  printf("%s, end: %lld, begin: %lld, diff: %lld\n",
	  my->hw_events[i].name,
	  (unsigned long long)end[INDEX_HW_COUNTERS + i],
	  (unsigned long long)begin[INDEX_HW_COUNTERS + i],
	  (unsigned long long)end[INDEX_HW_COUNTERS + i] - (unsigned long long)begin[INDEX_HW_COUNTERS + i]);
 }
}

结果

使用 perf list 可以查看有哪些perf事件可以测量,如下为测量branch-instructions branch-misses bus-cycles instructions cpu-cycles的结果

>./shim branch-instructions branch-misses bus-cycles instructions cpu-cycles ref-cycles
init shim thread at cpu 0
SHIM 0:creat 0 hardware event name:branch-instructions, fd:3, index:0
SHIM 0:creat 1 hardware event name:branch-misses, fd:4, index:1
SHIM 0:creat 2 hardware event name:bus-cycles, fd:5, index:2
SHIM 0:creat 3 hardware event name:instructions, fd:6, index:40000000
SHIM 0:creat 4 hardware event name:cpu-cycles, fd:7, index:3
SHIM 0:creat 5 hardware event name:ref-cycles, fd:8, index:40000002
updateindex event branch-instructions, fd 3, index 0
updateindex event branch-misses, fd 4, index 1
updateindex event bus-cycles, fd 5, index 2
updateindex event instructions, fd 6, index 40000000
updateindex event cpu-cycles, fd 7, index 3
updateindex event ref-cycles, fd 8, index 40000002
hello world
Trustable samples:1
branch-instructions, end: 45704, begin: 45209, diff: 495
branch-misses, end: 1474, begin: 1395, diff: 79
bus-cycles, end: 17373, begin: 16699, diff: 674
instructions, end: 143085, begin: 140616, diff: 2469
cpu-cycles, end: 114990, begin: 104869, diff: 10121
ref-cycles, end: 75271, begin: 57747, diff: 17524