为什么 ls 的这些指令数相差如此之大? (ptrace 对比 perf 对比 qemu)

Why do these instruction counts of ls differ so much? (ptrace vs perf vs qemu)

我想统计运行/bin/ls时执行的指令总数。 我使用了 3 种方法,它们的结果差异很大,我不知道为什么。

1.使用 ptrace

进行指令计数

我写了一段代码调用 ls 的实例并使用 ptrace 单步执行:

#include <stdio.h>
#include <sys/ptrace.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <sys/user.h>
#include <sys/reg.h>    
#include <sys/syscall.h>

int main()
{   
    pid_t child;
    child = fork(); //create child
    
    if(child == 0) {
        ptrace(PTRACE_TRACEME, 0, NULL, NULL);
        char* child_argv[] = {"/bin/ls", NULL};
        execv("/bin/ls", child_argv);
    }
    else {
        int status;
        long long ins_count = 0;
        while(1)
        {
            //stop tracing if child terminated successfully
            wait(&status);
            if(WIFEXITED(status))
                break;

                ins_count++;
                ptrace(PTRACE_SINGLESTEP, child, NULL, NULL);
        }

    printf("\n%lld Instructions executed.\n", ins_count);

    }
    
    return 0;
}

运行 这段代码让我执行了 516.678 条指令。

2。 QEMU 单步执行

我在单步模式下使用 qemu 模拟 ls,并使用以下命令将所有传入指令记录到日志文件中: qemu-x86_64 -singlestep -D logfile -d in_asm /bin/ls

根据 qemu ls 执行了 16.836 条指令。

3。性能

sudo perf stat ls

这个命令让我执行了 8.162.180 条指令。

我知道这些指令中的大部分来自动态链接器,它们被计算在内很好。但为什么这些数字相差如此之大?他们不应该都一样吗?

为什么这些指令数相差如此之大?因为他们衡量的东西真的不一样,只是衡量的单位是一样的。就好像你在称你从商店买来的东西,一个人称所有东西都没有包装,连标签都没有,另一个人在包装中称,连购物袋都包括在内,还有一个人还把你带进的泥加进去房子在你的靴子上。

这几乎就是这里发生的事情:指令计数不仅仅是 ls 二进制文件中的指令计数,还可以包括它使用的库、内核加载程序所需的服务引入这些库,最后在进程中但在内核上下文中执行代码。您使用的方法在这方面的表现各不相同。所以问题是:您需要从该测量中得到什么?如果您需要“总的努力”,那么最大的数字肯定是您想要的:这将包括一些由内核引起的开销。如果你需要“我只想知道ls发生了什么”,那么最小的就是你要的那个

你用qemu计算指令数的方法是错误的,in_asm选项只显示编译块中的翻译指令,所以在qemu中tb链接过程后,它会直接跳转到翻译块,导致qemu 中的计数少于其他工具,因此实践中的一个好方法是 -d nochain,exec-singlestep 选项。

不过,这些工具之间也存在指令编号差异,我已经尝试在不同的目录中使用 qemu 运行 来生成这些日志,qemu 来宾程序是静态链接的,日志文件在计数时显示不同的结果指令号,可能是一些 glibc 启动或 init 的东西涉及到环境参数导致这种差异。

您使用 PTRACE_SINGLESTEP 的程序应该计算进程中执行的所有 user-space 指令。 syscall 指令算作一个,因为你不能 single-step 进入内核;这对 ptrace 是不透明的。

这应该与计算 user-space 条指令的 perf stat --all-userperf stat -e instructions:u 非常相似。 (可能在数以百万计的几条指令中算出相同的数)。该 perf 选项或 :u 事件修饰符告诉它对硬件性能计数器进行编程以仅在 CPU 不处于特权级别 0(内核模式)时对事件进行计数;现代 x86 CPUs 对此有硬件支持,因此 perf 不必在内核中 运行 指令在每次转换时停止和重新启动计数器。

这两者都包括 user-space 中发生的所有事情,包括 ld-linux.so 动态链接器代码 运行s 在执行前到达 _start 动态可执行文件。

另请参阅 How do I determine the number of x86 machine instructions executed in a C program?,其中包括 hand-written 静态可执行文件的 asm 源代码,在 user-space 中只有 运行s 2 条指令。 perf stat --all-user 在我的 Skylake 上计算了 3 条指令。该问答还有很多关于 user-space 过程中发生的事情的其他讨论,以及希望有用的链接。


Qemu 计数完全不同,因为它进行动态翻译。请参阅 and Peter Maydell 在此处对 Kuba 的回答的评论中链接。

如果您想使用这样的工具,您可能需要使用英特尔 PIN 动态检测的英特尔 SDE。它可以为您绘制指令类型直方图,以及计算总数。请参阅我在 How do I determine the number of x86 machine instructions executed in a C program? 上的回答以获取链接。