【发布时间】:2014-08-19 21:30:33
【问题描述】:
我想计算某些代码的(或多或少)确切的指令数量。此外,我想在特定数量的指令通过后接收信号。
为此,我使用了提供的溢出信号行为 perf_event_open.
我正在使用手册页建议的第二种方式来实现溢出信号:
信号溢出
可以设置事件以在达到阈值时传递信号 被交叉。信号处理程序是使用 poll(2)、select(2)、 epoll(2) 和 fcntl(2),系统调用。
[...]
另一种方法是使用 PERF_EVENT_IOC_REFRESH ioctl。这 ioctl 添加到每次事件溢出时递减的计数器。 当非零时,溢出时发送 POLL_IN 信号,但 一旦值 达到 0,发送 POLL_HUP 类型的信号和基础事件 已禁用。
PERF_EVENT_IOC_REFRESH ioctl的进一步解释:
PERF_EVENT_IOC_REFRESH
非继承的溢出计数器可以使用它来启用 参数指定的溢出次数的计数器, 之后它被禁用。此 ioctl 的后续调用 将参数值添加到当前计数。一个信号 POLL_IN 设置将在每次溢出时发生,直到计数 达到0;当发生这种情况时,设置了 POLL_HUP 的信号是 发送并且事件被禁用。使用 0 的参数是 被认为是未定义的行为。
一个非常简单的示例如下所示:
#define _GNU_SOURCE 1
#include <asm/unistd.h>
#include <fcntl.h>
#include <linux/perf_event.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
long perf_event_open(struct perf_event_attr* event_attr, pid_t pid, int cpu, int group_fd, unsigned long flags)
{
return syscall(__NR_perf_event_open, event_attr, pid, cpu, group_fd, flags);
}
static void perf_event_handler(int signum, siginfo_t* info, void* ucontext) {
if(info->si_code != POLL_HUP) {
// Only POLL_HUP should happen.
exit(EXIT_FAILURE);
}
ioctl(info->si_fd, PERF_EVENT_IOC_REFRESH, 1);
}
int main(int argc, char** argv)
{
// Configure signal handler
struct sigaction sa;
memset(&sa, 0, sizeof(struct sigaction));
sa.sa_sigaction = perf_event_handler;
sa.sa_flags = SA_SIGINFO;
// Setup signal handler
if (sigaction(SIGIO, &sa, NULL) < 0) {
fprintf(stderr,"Error setting up signal handler\n");
perror("sigaction");
exit(EXIT_FAILURE);
}
// Configure perf_event_attr struct
struct perf_event_attr pe;
memset(&pe, 0, sizeof(struct perf_event_attr));
pe.type = PERF_TYPE_HARDWARE;
pe.size = sizeof(struct perf_event_attr);
pe.config = PERF_COUNT_HW_INSTRUCTIONS; // Count retired hardware instructions
pe.disabled = 1; // Event is initially disabled
pe.sample_type = PERF_SAMPLE_IP;
pe.sample_period = 1000;
pe.exclude_kernel = 1; // excluding events that happen in the kernel-space
pe.exclude_hv = 1; // excluding events that happen in the hypervisor
pid_t pid = 0; // measure the current process/thread
int cpu = -1; // measure on any cpu
int group_fd = -1;
unsigned long flags = 0;
int fd = perf_event_open(&pe, pid, cpu, group_fd, flags);
if (fd == -1) {
fprintf(stderr, "Error opening leader %llx\n", pe.config);
perror("perf_event_open");
exit(EXIT_FAILURE);
}
// Setup event handler for overflow signals
fcntl(fd, F_SETFL, O_NONBLOCK|O_ASYNC);
fcntl(fd, F_SETSIG, SIGIO);
fcntl(fd, F_SETOWN, getpid());
ioctl(fd, PERF_EVENT_IOC_RESET, 0); // Reset event counter to 0
ioctl(fd, PERF_EVENT_IOC_REFRESH, 1); //
// Start monitoring
long loopCount = 1000000;
long c = 0;
long i = 0;
// Some sample payload.
for(i = 0; i < loopCount; i++) {
c += 1;
}
// End monitoring
ioctl(fd, PERF_EVENT_IOC_DISABLE, 0); // Disable event
long long counter;
read(fd, &counter, sizeof(long long)); // Read event counter value
printf("Used %lld instructions\n", counter);
close(fd);
}
所以基本上我正在做以下事情:
- 为 SIGIO 信号设置信号处理程序
- 使用
perf_event_open创建一个新的性能计数器(返回一个文件描述符) - 使用
fcntl将信号发送行为添加到文件描述符。 - 运行有效负载循环以执行许多指令。
在执行有效负载循环时,有时会执行 1000 条指令(sample_interval)。根据perf_event_open manpage,这会触发溢出,然后递减内部计数器。
一旦此计数器达到零,“将发送 POLL_HUP 类型的信号,并禁用底层事件。”
发送信号时,停止当前进程/线程的控制流,并执行信号处理程序。场景:
- 已执行 1000 条指令。
- 事件自动禁用并发送信号。
- 信号立即传递,进程的控制流停止并执行信号处理程序。
这种情况意味着两件事:
- 计数指令的最终数量将始终等于与根本不使用信号的示例。
- 为信号处理程序保存的指令指针(可通过
ucontext访问)将直接指向导致溢出的指令。
基本上你可以说,信号行为可以被视为同步。
这是我想要实现的完美语义。
但是,就我而言,我配置的信号通常是相当异步的,并且可能需要一段时间才能最终交付并执行信号处理程序。这可能会给我带来问题。
例如,考虑以下场景:
- 已执行 1000 条指令。
- 事件自动禁用并发送信号。
- 更多指令通过
- 信号已传递,进程的控制流停止并执行信号处理程序。
这种情况意味着两件事:
- 计数指令的最终数量将少于一个完全不使用信号的示例。
- 为信号处理程序保存的指令指针将指向导致溢出的指令或指向它之后的任何指令。
到目前为止,我已经对上面的示例进行了很多测试,并且没有遇到遗漏的说明,这将支持第一种情况。
但是,我真的很想知道,我是否可以依赖这个假设。 内核中发生了什么?
【问题讨论】:
-
如果你想“计算(或多或少)确切数量的某些代码的指令数量”,不要使用采样,而是使用硬件 pmu 的计数模式。使用 perf 时,
perf_event_attr中没有设置sample_period/sample_freq。如果您想获得溢出事件的确切 IP,请使用precise_ip标志设置为 1 或 2。如果性能不允许您同时进行采样和计数,请尝试likwid-perfctr(来自 github.com/RRZE- HPC/likwid)并检查您的 CPU 的 PMU 文档(是 x86、intel/amd 还是系列?)。我仍然不清楚真正的问题是什么,是否可以回答。