9

有没有一种简单的方法可以在执行 C 程序时快速计算执行的指令数量(x86 指令 - 每个指令和多少)?

gcc version 4.7.1 (GCC)x86_64 GNU/Linux机器上使用。

4

5 回答 5

5

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 上测试。

于 2020-11-16T18:10:10.977 回答
2

可能是这个问题的副本

我说可能是因为您询问了汇编程序指令,但该问题处理了 C 级代码分析。

但是,我对您的问题是:您为什么要分析实际执行的机器指令?作为第一个问题,这在各种编译器及其优化设置之间会有所不同。作为一个更实际的问题,您实际上可以用这些信息做什么?如果您正在搜索/优化瓶颈,代码分析器就是您正在寻找的。

不过,我可能会在这里错过一些重要的事情。

于 2012-11-09T18:08:35.723 回答
2

您可以使用硬件性能计数器 (HPC) 轻松计算执行指令的数量。为了访问 HPC,您需要一个接口。我推荐你使用 PAPI 性能 API。

于 2016-12-18T23:44:11.860 回答
2

英特尔引脚instcount

您可以使用 Intel 的二进制检测工具“ Pin ”。我会避免使用模拟器(它们通常非常慢)。Pin 完成了您可以使用模拟器完成的大部分工作,而无需重新编译二进制文件并以正常的执行速度(取决于您使用的 pin 工具)。

用 Pin 计算指令的数量:

  1. 从此处下载最新的(或 3.10,如果此答案过时)针套件。
  2. 提取所有内容并转到目录:cd pin-root/source/tools/ManualExample/
  3. 制作目录中的所有工具:make all
  4. 使用以下命令运行名为 inscount0.so 的工具:../../../pin -t obj-intel64/inscount0.so -- your-binary-here
  5. 获取文件中的指令计数inscount.outcat 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

于 2019-06-22T07:56:26.233 回答
1

虽然取决于程序不是“快速”,但这可能已经在这个问题中得到了回答。在这里,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函数的返回地址。即程序执行结束。

于 2019-04-18T07:04:17.033 回答