评测一个程序的性能有多种方法,对目前大多数benchmark,程序执行时间是一个很重要的标准。但大多数情况下时间仅仅能给出一个结果,却给不了更多的信息,如cache miss, CPU功能部件的利用率,访存指令,分支预测等。这些信息对于知道一个程序的优化往往会起到很重要的作用。


很多现代的CPU都有一些内置的计数器用来记录这些事件,X86环境下可以调用一些库对这些计数器进行设置和访问,具体可以参考
这里。关于X86下使用performance counter的方法很多,而且资料也很容易找到,大家google一下就好了。


龙芯从早一个版本(loongson 2e)就提供了性能计数器,龙芯2f的计数器基本同2e大概上没有什么区别,定义了两个性能计数器(performance counter),分别映射到CP0(协处理器)的第24、25号寄存器。25号寄存器是64位,其高32位用作counter1, 对应的event1, 低32位用作counter0, 对应event0, event0和event1是由24号寄存器的5-12位控制的,其中5-8位为event0,9-12位为event1,用户可以通过设置24号寄存器相应的位来控制counter记录的事件,event0和event1对应的事件如下所示:

event0:
0000                 周期
0001                 分支指令
0010                 指令
0011                 指令并且域rs=31
0100                 一级I-cache 缺失
0101                  Alu1 操作已发射
0110                 Mem 操作已发射
0111                 Falu1 操作已发射
1000                  猜测指令
1001                 从主存中读
1010                 固定发射队列满的次数
1011                 重排队列满的次数
1100                  CP0 队列满的次数
1101                 Tlb 重填例外
1110                 例外
1111                 内部例外
event1:
0000                 提交操作
0001                 分支预测失败
0010                 预测失败
0011                 JR 且rs=31 预测失败
0100                 一级D-cache 缺失
0101                 Alu2 操作已发射
0110                 Falu2 操作已发射
0111                 Uncached 访问
1000                 BHT 猜测错误
1001                 写到主存
1010                 浮点指针队列满的次数
1011                 分支队列满的次数
1100                 Itlb 缺失
1101                 例外总数
1110                  投机缺失
1111                  队列向前加载

要使用计数器,首先要设置24号寄存器中event0和event1所对应的域。比如你要测一级I-Cache miss和一级D-Cache miss,那么就要把24号寄存器设置成0x44f(0-4位以及其他位为默认值)。但是有一个问题,龙芯2f协处理器只有在内核态下才能更改,因此我们要更改24号寄存器,需要模块的帮助。


龙芯的协处理器是通过MFC0/DMFC0来读和通过MFT0/DMFT0来写的,如果在用户态下使用MFT0/DMFT0指令,会在运行时得到“非法指 令”的错误信息。即便是在内核态下,[loongson_counter.c]

#include <linux/init.h>
 
#include <linux/module.h>
 
MODULE_LICENSE("Dual BSD/GPL");
 
static int event_init(void)
 
{
 
    unsigned int event = 0x44f;
 
    asm volatile("mtc0 %0, $24"::"r"(event));
 
    return 0;
 
}
 
static void event_exit(void)
 
{
 
}
 
module_init(set_init);
 
module_exit(set_exit);

[makefile]

ifneq ($(KERNELRELEASE),)
 
obj-m :=loongson_counter.o
 
else
 
KERNELDIR ?=/lib/modules/$(shell uname -r)/build
 
PWD :=$(shell pwd)
 
clean:
 
rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions
 
default:
 
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
 
endif

make生成模块后,使用insmod loongson_counter.ko将模块加载,这时就可以使用25号寄存器记录I-cache miss和D-cache miss了。

在具体程序中,如果你要计算一个函数带来的cache miss,可以在这个函数开头通过MFC0/DMFC0指令读取计数器中的初始值,然后在函数结尾,再读取一次,两次相减就得到了该函数造成的cache miss。注意硬件性能计数器是一个全局的计数器,即如果你在运行你要测试的函数同时运行了其他程序,其他程序造成的cache miss也会记录在计数器里。所以在保持cpu独占的情况下可以测试几次取平均值。下面以一个sort()函数为例示例怎么在函数中应用:

[sort.c]

void sort(){
 
        usigned int icache_init, dcache_initi,
 
        cache_miss, dcache_miss;
 
        asm volatile(".set mips3\n\t"
 
                "dmfc0 %0, $25\n\t"
 
                "dsra   %0, 32\n\t"
 
                "mfc0   %0, $25\n\t"
 
                :"=r"(dcache_init),"=r"(icache_init)
 
        );  //把counter的初始值记录下来
 
        qsort(........);
 
        ...........;
 
        asm volatile(".set mips3\n\t"
 
                "dmfc0 %0, $25\n\t"
 
                "dsra   %0, 32\n\t"
 
                "mfc0   %0, $25\n\t"
 
                :"=r"(dcache_miss),"=r"(icache_miss)
 
        );  //记录函数运行后计数器的值
 
        fprintf(stderr, "I cache miss is:%d\n", icache_miss - icache_init);
 
        fprintf(stderr, "D cache miss is:%d\n", dcache_miss - dcache_init);
 
}

现在我们已经可以使龙芯2f的计数器起作用了,但是还存在一个问题,如果我有一个代码量相对比较多的程序,想做一个工具来多方面的评测这个程序,许要使用 到各种不同的计数器事件,而又不想每次都重新编译加载一个内核来改变这个事件,这时候就需要写一个相对稍复杂一些的模块用来设置事件,下面是我写的一个模 块,可以通过write系统调用来改变事件。

[loongson_counter.c]

#include <linux/init.h>
 
#include <linux/modules.h>
 
#include <linux/fs.h>
 
#include <linux/cdev.h>
 
MODULE_LICENSE("Dual BSD/GPL");
 
MODULE_AUTHOR("sponge");
 
MODULE_DESCRIPTION("change event of performance counter");
 
MODULE_ALIAS("set_event");
 
//function convert char to int
 
int convert_event(char *array){
 
        int ret = 0;
 
        int i;
 
        for(i = 0; i&lt;=3; i++){
 
                //printk("ret is :%x, i is %x, array[i] is %c\n", ret, i , array[i]);
 
                if(array[i]=='1')
 
                        ret = ret + (1&lt;&lt;(3-i));
 
                else if(array[i]=='0')
 
                        continue;
 
                else
 
                        return -EINVAL;
 
        }
 
        return ret;
 
}
 
//function used to modify the $24 register
 
int event_write(struct file *filp, const char __user *buff, size_t count,
 
loff_t *f_pos){
 
        int e0, e1; //variables hold the event number
 
        char buff_k[9]; //buffer holds date transform from user space
 
        char event0[5];
 
        char event1[5];//these two array hold event number in form of char
 
        int i; //loop counter
 
        int regval;// the value that will transform to $24
 
        if(count != 9)
 
        return -EINVAL;
 
        //copy user date
 
        if(copy_from_user(buff_k, buff, 9))
 
                return -EFAULT;
 
        //convert user date to array
 
        for(i = 0; i&lt;=3; i++){
 
                event0[i] = buff_k[i];
 
                event1[i] = buff_k[i+4];
 
        }
 
        //convert char to int
 
        if((e0 = convert_event(event0))==-EINVAL)
 
                return -EINVAL;
 
        if((e1 = convert_event(event1))==-EINVAL)
 
                return -EINVAL;
 
        //printk("event0 is %x, event1 is %x\n",e0,e1);
 
        //compute $24
 
        regval = (e0&lt;&lt;5)|(e1&lt;&lt;9)|0xf;
 
        asm volatile("mtc0 %0, $24"::"r"(regval));
 
        //unsigned int val = 0;
 
        //asm volatile("mfc0 %0, $24":"=r"(val));
 
        //printk(KERN_EMERG "current cp0_24 value is 0x%x\n", val);
 
        return 0;
 
}
 
struct cdev event;
 
struct file_operations event_ops;
 
//init function
 
static int init_event(void){
 
        //register the device event
 
        if(register_chrdev_region(MKDEV(33,0),1,"pcounter")){
 
                return -EFAULT;
 
        }
 
        //register operations
 
        event_ops.write = event_write;
 
        cdev_init(&amp;event, &amp;event_ops);
 
        cdev_add(&amp;event,MKDEV(33,0),1);
 
        return 0;
 
}
 
//clean function
 
static void exit_event(void){
 
        //unregister device
 
        cdev_del(&amp;event);
 
        unregister_chrdev_region(MKDEV(33,0),1);
 
}
 
module_init(init_event);
 
module_exit(exit_event);

Makefile跟上一个例子的基本一致。

使用该模块的方法为:

1、在root下使用命令 mknod /dev/pcounter c 33 0

2、在root下使用命令 chmod 666 /dev/pcounter

3、编译该模块,使用insmod 加载模块

4、在程序中可以通过write系统调用来更改性能计数器的事件,代码如下:

 #define writelong 9
 
int fd;
 
fd=open("/dev/pcounter, O_RDWR");
 
write(fd, "01000100", writelong);  //写入的8个字符,前4位表示event0, 后4位表示event1
anyShare分享到:

2 Responses to “使用龙芯2f性能计数器评测程序性能”

  1. littlebeauty says:

    Good!Fighting!

Leave a Reply to littlebeauty

Note: Commenter is allowed to use '@User+blank' to automatically notify your reply to other commenter. e.g, if ABC is one of commenter of this post, then write '@ABC '(exclude ') will automatically send your comment to ABC. Using '@all ' to notify all previous commenters. Be sure that the value of User should exactly match with commenter's name (case sensitive).