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

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

我想看看它在处理器中运行了多长时间,有多少缓存命中 + 错过了它。有关上下文切换和内存使用的信息也很好。

程序执行不到一秒钟。

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

有什么想法吗?

编辑: 我认为瓦尔格恩增加了很多开销。这就是为什么我需要一个简单的工具,比如/proc/[ pid ]/stat,它总是在那里。

53796 次浏览

最适合你的工具是 瓦尔格林。它能够进行内存分析,呼叫图构建等等。

sudo apt get install valgrind
valgrind ./yourapp

但是,要获得程序执行的时间,可以使用 time(8) Linux 实用程序。

time ./yourapp

使用 完美:

perf stat ./yourapp

请参阅 内核 wiki perf 教程了解详细信息。它使用 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 的软件包) ,它应该可以正常工作。使用 perf record -a/perf report组合,您还可以进行全系统分析。任何具有调试符号的应用程序或库都将在报表中显示详细信息。

对于可视化 火焰图表似乎工作得很好。(更新2020: 热点用户界面集成了火焰图。)

你也可以用

/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 标志来格式化输出以满足您的需要。

请务必使用它的完整路径调用这个程序,否则它将调用“时间”命令,这不是你需要的..。

希望这个能帮上忙!

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

https://stackoverflow.com/a/10114325/895245所示,perf很可能是 OP 想要的,但是为了完整起见,我将展示如何在 C 程序中控制源代码。

这种方法可以允许在程序中对特定感兴趣的区域进行更精确的测量。它还可以为每个不同的缓存级别获取单独的缓存命中/未命中计数。这个系统调用可能与 perf共享相同的后端。

这个例子基本上与 计算 C 程序中执行的指令数的快速方法相同,但是与 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,几乎完全匹配我的计算机上64 根据 getconf LEVEL1_DCACHE_LINESIZE的精确值,所以我猜它是工作的。