【发布时间】:2021-02-11 14:28:01
【问题描述】:
我想获取在 Linux 机器上运行的 C/C++ 程序 (foo) 的特定功能的缓存命中率。我正在使用 gcc 并且没有编译器优化。使用 perf 我可以使用以下命令获取整个程序的命中率。
perf stat -e L1-dcache-loads,L1-dcache-load-misses,L1-dcache-stores,L1-dcache-store-misses ./a.out
但我只对内核 foo 感兴趣。
有没有办法使用 perf 或任何其他工具仅获得 foo 的命中率?
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <math.h>
#include <stdlib.h>
#define NI 192
#define NJ NI
#ifndef DATA_TYPE
#define DATA_TYPE float
#endif
static
void* xmalloc(size_t num)
{
void * nnew = NULL;
int ret = posix_memalign (&nnew, 32, num);
if(!nnew || ret)
{
fprintf(stderr, "Can not allocate Memory\n");
exit(1);
}
return nnew;
}
void* alloc_data(unsigned long long int n, int elt_size)
{
size_t val = n;
val *= elt_size;
void* ret = xmalloc(val);
return ret;
}
/* Array initialization. */
static
void init_array(int ni, int nj,
DATA_TYPE A[NI][NJ],
DATA_TYPE R[NJ][NJ],
DATA_TYPE Q[NI][NJ])
{
int i, j;
for (i = 0; i < ni; i++)
for (j = 0; j < nj; j++) {
A[i][j] = ((DATA_TYPE) i*j) / ni;
Q[i][j] = ((DATA_TYPE) i*(j+1)) / nj;
}
for (i = 0; i < nj; i++)
for (j = 0; j < nj; j++)
R[i][j] = ((DATA_TYPE) i*(j+2)) / nj;
}
/* Main computational kernel.*/
static
void foo(int ni, int nj,
DATA_TYPE A[NI][NJ],
DATA_TYPE R[NJ][NJ],
DATA_TYPE Q[NI][NJ])
{
int i, j, k;
DATA_TYPE nrm;
for (k = 0; k < nj; k++)
{
nrm = 0;
for (i = 0; i < ni; i++)
nrm += A[i][k] * A[i][k];
R[k][k] = sqrt(nrm);
for (i = 0; i < ni; i++)
Q[i][k] = A[i][k] / R[k][k];
for (j = k + 1; j < nj; j++)
{
R[k][j] = 0;
for (i = 0; i < ni; i++)
R[k][j] += Q[i][k] * A[i][j];
for (i = 0; i < ni; i++)
A[i][j] = A[i][j] - Q[i][k] * R[k][j];
}
}
}
int main(int argc, char** argv)
{
/* Retrieve problem size. */
int ni = NI;
int nj = NJ;
/* Variable declaration/allocation. */
DATA_TYPE (*A)[NI][NJ];
DATA_TYPE (*R)[NI][NJ];
DATA_TYPE (*Q)[NI][NJ];
A = ((DATA_TYPE (*)[NI][NJ])(alloc_data((NI*NJ), (sizeof(DATA_TYPE)))));
R = ((DATA_TYPE (*)[NI][NJ])(alloc_data((NI*NJ), (sizeof(DATA_TYPE)))));
Q = ((DATA_TYPE (*)[NI][NJ])(alloc_data((NI*NJ), (sizeof(DATA_TYPE)))));
/* Initialize array(s). */
init_array (ni, nj,
(*A),
(*R),
(*Q));
/* Run kernel. */
foo (ni, nj, *A, *R, *Q);
/* Be clean. */
free((void *)A);
free((void *)R);
free((void *)Q);
return 0;
}
lscpu 命令的输出是:
Architecture: x86_64
CPU op-mode(s): 32-bit, 64-bit
Byte Order: Little Endian
CPU(s): 16
On-line CPU(s) list: 0-15
Thread(s) per core: 2
Core(s) per socket: 8
Socket(s): 1
NUMA node(s): 1
Vendor ID: GenuineIntel
CPU family: 6
Model: 63
Model name: Intel(R) Core(TM) i7-5960X CPU @ 3.00GHz
Stepping: 2
CPU max MHz: 3500.0000
CPU min MHz: 1200.0000
L1d cache: 32K
L1i cache: 32K
L2 cache: 256K
L3 cache: 20480K
NUMA node0 CPU(s): 0-15
【问题讨论】:
-
写一个只运行
foo()的程序并测量它? -
您想要的是卡尺测量:调用 foo() 之前的“开始计数器”和 foo() 结束时的“停止计数器”。为此,您需要检测代码并重新构建它。获取计数器的能力取决于处理器架构及其 PMU。获取计数器的方法是特定于供应商的。这就是为什么像 papi 这样的库非常有用,因为它们透明地支持多个处理器/PMU 架构。为什么你不能使用 papi ?
-
@hyde:这将包括动态链接和分配/初始化部分的计数。您可以通过使用
perf stat --all-user(或使用较旧的perf,使用event:u,event:u,...)仅计算用户空间所以是的,如果您可以重复foo很多次以淹没背景,那么您可以对整个程序进行计时初始化工作的噪音;如果它可以运行多次而不重做它的初始化。但是,如果您想使用包含大量初始化时间的大型数组运行foo,这可能是不切实际的。 -
@PeterCordes 可以使用静态链接。可以预先计算数组。
-
但是当我尝试使用 PAPI_add_event 函数添加这些事件时,这将返回错误代码 -8(事件存在,但由于计数器资源限制而无法计数)。当我尝试添加三个事件时它失败了。如果我只添加两个事件,它工作正常。
标签: c linux perf papi intel-pmu