有没有一种简单的方法可以在执行 C 程序时快速计算执行的指令数量(x86 指令 - 每个指令和多少)?
我gcc version 4.7.1 (GCC)
在x86_64 GNU/Linux
机器上使用。
Linuxperf_event_open
系统调用与config = PERF_COUNT_HW_INSTRUCTIONS
这个 Linux 系统调用似乎是性能事件的跨体系结构包装器,包括来自 CPU 的硬件性能计数器和来自内核的软件事件。
这是一个改编自man perf_event_open
页面的示例:
perf_event_open.c
#define _GNU_SOURCE
#include <asm/unistd.h>
#include <linux/perf_event.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <inttypes.h>
#include <sys/types.h>
static long
perf_event_open(struct perf_event_attr *hw_event, pid_t pid,
int cpu, int group_fd, unsigned long flags)
{
int ret;
ret = syscall(__NR_perf_event_open, hw_event, pid, cpu,
group_fd, flags);
return ret;
}
int
main(int argc, char **argv)
{
struct perf_event_attr pe;
long long count;
int fd;
uint64_t n;
if (argc > 1) {
n = strtoll(argv[1], NULL, 0);
} else {
n = 10000;
}
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;
pe.disabled = 1;
pe.exclude_kernel = 1;
// Don't count hypervisor events.
pe.exclude_hv = 1;
fd = perf_event_open(&pe, 0, -1, -1, 0);
if (fd == -1) {
fprintf(stderr, "Error opening leader %llx\n", pe.config);
exit(EXIT_FAILURE);
}
ioctl(fd, PERF_EVENT_IOC_RESET, 0);
ioctl(fd, PERF_EVENT_IOC_ENABLE, 0);
/* Loop n times, should be good enough for -O0. */
__asm__ (
"1:;\n"
"sub $1, %[n];\n"
"jne 1b;\n"
: [n] "+r" (n)
:
:
);
ioctl(fd, PERF_EVENT_IOC_DISABLE, 0);
read(fd, &count, sizeof(long long));
printf("Used %lld instructions\n", count);
close(fd);
}
编译并运行:
g++ -ggdb3 -O0 -std=c++11 -Wall -Wextra -pedantic -o perf_event_open.out perf_event_open.c
./perf_event_open.out
输出:
Used 20016 instructions
所以我们看到结果非常接近预期值 20000:10k *__asm__
块中每个循环的两条指令(sub
, jne
)。
如果我改变论点,即使是低值,例如100
:
./perf_event_open.out 100
它给:
Used 216 instructions
保持恒定+ 16条指令,所以看起来准确度很高,那16条一定只是ioctl
我们小循环之后的设置指令。
现在您可能还对以下内容感兴趣:
可以通过此系统调用测量的其他感兴趣的事件:
在 Ubuntu 20.04 amd64、GCC 9.3.0、Linux 内核 5.4.0、Intel Core i7-7820HQ CPU 上测试。
可能是这个问题的副本
我说可能是因为您询问了汇编程序指令,但该问题处理了 C 级代码分析。
但是,我对您的问题是:您为什么要分析实际执行的机器指令?作为第一个问题,这在各种编译器及其优化设置之间会有所不同。作为一个更实际的问题,您实际上可以用这些信息做什么?如果您正在搜索/优化瓶颈,代码分析器就是您正在寻找的。
不过,我可能会在这里错过一些重要的事情。
您可以使用硬件性能计数器 (HPC) 轻松计算执行指令的数量。为了访问 HPC,您需要一个接口。我推荐你使用 PAPI 性能 API。
instcount
您可以使用 Intel 的二进制检测工具“ Pin ”。我会避免使用模拟器(它们通常非常慢)。Pin 完成了您可以使用模拟器完成的大部分工作,而无需重新编译二进制文件并以正常的执行速度(取决于您使用的 pin 工具)。
用 Pin 计算指令的数量:
cd pin-root/source/tools/ManualExample/
make all
../../../pin -t obj-intel64/inscount0.so -- your-binary-here
inscount.out
,cat inscount.out
.输出将类似于:
➜ ../../../pin -t obj-intel64/inscount0.so -- /bin/ls
buffer_linux.cpp itrace.cpp
buffer_windows.cpp little_malloc.c
countreps.cpp makefile
detach.cpp makefile.rules
divide_by_zero_unix.c malloc_mt.cpp
isampling.cpp w_malloctrace.cpp
➜ cat inscount.out
Count 716372
虽然取决于程序不是“快速”,但这可能已经在这个问题中得到了回答。在这里,Mark Plotnick 建议使用gdb
来观察程序计数器寄存器的变化:
# instructioncount.gdb
set pagination off
set $count=0
while ($pc != 0xyourstoppingaddress)
stepi
set $count++
end
print $count
quit
然后,开始gdb
你的程序:
gdb --batch --command instructioncount.gdb --args ./yourexecutable with its arguments
要获取结束地址0xyourstoppingaddress
,您可以使用以下脚本:
# stopaddress.gdb
break main
run
info frame
quit
在函数 上放置一个断点main
,并给出:
$ gdb --batch --command stopaddress.gdb --args ./yourexecutable with its arguments
...
Stack level 0, frame at 0x7fffffffdf70:
rip = 0x40089d in main (main_aes.c:33); saved rip 0x7ffff7a66d20
source language c.
Arglist at 0x7fffffffdf60, args: argc=3, argv=0x7fffffffe048
...
这里重要的是saved rip 0x7ffff7a66d20
部分。在我的 CPU 上,rip
是指令指针,saved rip
是“返回地址”,如 pepero在此答案中所述。
所以在这种情况下,停止地址是0x7ffff7a66d20
,也就是main
函数的返回地址。即程序执行结束。