在Linux中,测量C程序缓存命中/未命中和CPU时间的最简单工具是什么?

76

我正在用C语言编写一个小程序,想要测量它的性能。

我想要看到它在处理器上运行的时间以及它产生了多少高速缓存命中与未命中。同时了解有关上下文切换和内存使用情况的信息也很不错。

该程序执行时间不到1秒。

我喜欢/proc/[pid]/stat提供的信息,但我不知道如何在程序死亡/被杀死后查看它。

有什么想法吗?

编辑:我认为Valgrind增加了很多开销,这就是为什么我想要一个像/proc/[pid]/stat这样的简单工具,它总是存在的。


1
你是否在寻找类似这样的东西?http://software.intel.com/en-us/articles/intel-performance-counter-monitor/ - harold
4个回答

126
使用 perf:
perf stat ./yourapp

请参阅kernel wiki perf tutorial以获取详细信息。这将使用您的CPU硬件性能计数器,因此开销非常小。
来自维基的示例:
perf stat -B dd if=/dev/zero of=/dev/null count=1000000

Performance counter stats for 'dd if=/dev/zero of=/dev/null count=1000000':

        5,099 cache-misses             #      0.005 M/sec (scaled from 66.58%)
      235,384 cache-references         #      0.246 M/sec (scaled from 66.56%)
    9,281,660 branch-misses            #      3.858 %     (scaled from 33.50%)
  240,609,766 branches                 #    251.559 M/sec (scaled from 33.66%)
1,403,561,257 instructions             #      0.679 IPC   (scaled from 50.23%)
2,066,201,729 cycles                   #   2160.227 M/sec (scaled from 66.67%)
          217 page-faults              #      0.000 M/sec
            3 CPU-migrations           #      0.000 M/sec
           83 context-switches         #      0.000 M/sec
   956.474238 task-clock-msecs         #      0.999 CPUs

   0.957617512  seconds time elapsed

不需要手动加载内核模块,在现代Debian系统上(使用linux-base软件包),它应该可以自行运行。使用perf record -a / perf report组合,您还可以进行全系统分析。任何具有调试符号的应用程序或库都将在报告中显示详细信息。
对于可视化,flame graphs似乎效果很好。(2020年更新:hotspot用户界面已集成火焰图。)

11

最好的工具叫做valgrind。它可以进行内存分析、调用图构建等等。

sudo apt get install valgrind
valgrind ./yourapp

然而,要获取程序执行的时间,你可以使用time(8) Linux 实用程序。

time ./yourapp

1
据我所知,Valgrind 可以测量所有缓存级别,至少包括 L1 和 L2。 - iehrlich
4
有一个名为Cachegrind的Valgrind模块,用于测量缓存。 - Some programmer dude
我没有man 8 time的手册。time(8)是C函数吗?我需要CPU使用时间,而不是总运行时间。我认为Valgrind会增加很多开销。这就是为什么我想要一个简单的工具,像/proc/[pid]/stat,它总是存在的。 - jperelli
值得注意的是,/usr/bin/time != time。在bash中,time是一个内置的shell关键字。 - jperelli
5
值得注意的是,Cachegrind模拟缓存,而perf工具从实际缓存中测量。 - Alfredo Gimenez
显示剩余2条评论

9
您可以使用以下方法:

您还可以使用

/usr/bin/time -v YourProgram.exe

这将向您展示所有这些信息:

/usr/bin/time -v ls
    Command being timed: "ls"
    User time (seconds): 0.00
    System time (seconds): 0.00
    Percent of CPU this job got: 60%
    Elapsed (wall clock) time (h:mm:ss or m:ss): 0:00.00
    Average shared text size (kbytes): 0
    Average unshared data size (kbytes): 0
    Average stack size (kbytes): 0
    Average total size (kbytes): 0
    Maximum resident set size (kbytes): 4080
    Average resident set size (kbytes): 0
    Major (requiring I/O) page faults: 0
    Minor (reclaiming a frame) page faults: 314
    Voluntary context switches: 1
    Involuntary context switches: 1
    Swaps: 0
    File system inputs: 0
    File system outputs: 0
    Socket messages sent: 0
    Socket messages received: 0
    Signals delivered: 0
    Page size (bytes): 4096
    Exit status: 0

您还可以使用-f标志将输出格式化以适应您的需求。

请确保使用完整路径调用此程序,否则它将调用“time”命令,这不是您所需的...

希望这有所帮助!


9
值得注意的是,/usr/bin/timetime 不同。在 Bash 中,time 是一个内置的 shell 关键字。 - jperelli
22
缓存未命中在哪里? - devoured elysium
5
答案与缓存未命中无关。 - Jingguo Yao

7

使用 config = PERF_COUNT_HW_INSTRUCTIONS 的 Linux perf_event_open 系统调用

perf 很可能是 OP 想要的,如 https://dev59.com/y2kw5IYBdhLWcg3wKneb#10114325 所示,但为了完整起见,我将展示如何在 C 程序内进行此操作(如果您控制源代码)。

该方法可以允许更精确地测量程序内特定区域。同时它也可以获得每个不同缓存级别的单独缓存命中/失效计数。这个系统调用很可能与 perf 共享同样的后端。

这个示例基本上与 Quick way to count number of instructions executed in a C program 相同,只不过使用了 PERF_TYPE_HW_CACHE。通过以下方式实现:

man perf_event_open

在这些示例中,我们仅计算:

  • L1数据缓存(PERF_COUNT_HW_CACHE_L1D
  • 读取(PERF_COUNT_HW_CACHE_OP_READ),而不是预取写入
  • 缺失(PERF_COUNT_HW_CACHE_RESULT_MISS),而不是命中

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 <sys/types.h>
#include <sys/syscall.h>
#include <unistd.h>

#include <inttypes.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;
    char *chars, c;

    uint64_t n;
    if (argc > 1) {
        n = strtoll(argv[1], NULL, 0);
    } else {
        n = 10000;
    }

    chars = malloc(n * sizeof(char));

    memset(&pe, 0, sizeof(struct perf_event_attr));
    pe.type = PERF_TYPE_HW_CACHE;
    pe.size = sizeof(struct perf_event_attr);
    pe.config = PERF_COUNT_HW_CACHE_L1D |
                PERF_COUNT_HW_CACHE_OP_READ << 8 |
                PERF_COUNT_HW_CACHE_RESULT_MISS << 16;
    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);
    }

    /* Write the memory to ensure misses later. */
    for (size_t i = 0; i < n; i++) {
        chars[i] = 1;
    }

    ioctl(fd, PERF_EVENT_IOC_RESET, 0);
    ioctl(fd, PERF_EVENT_IOC_ENABLE, 0);

    /* Read from memory. */
    for (size_t i = 0; i < n; i++) {
        c = chars[i];
    }

    ioctl(fd, PERF_EVENT_IOC_DISABLE, 0);
    read(fd, &count, sizeof(long long));

    printf("%lld\n", count);

    close(fd);
    free(chars);
}

有了这个功能,我的结果会呈线性增长,例如:

./main.out 100000
# 1565
./main.out 1000000
# 15632
./main.out 10000000
# 156641

由此我们可以估算缓存行大小为: 100000/1565 ~ 63.9,几乎完全匹配我的电脑上根据getconf LEVEL1_DCACHE_LINESIZE得出的确切值64(参见此处),所以我猜测它正在工作。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接