3

我想获得在 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
4

3 回答 3

2

您还可以使用Likwid及其Marker-API。它使检测代码的某些区域变得非常容易。您可以使用 haswell 架构上预定义的性能组 ICACHE来获取 L1 缓存未命中率,也可以为 L1 命中率定义自己的性能组。

#include likwid.h
LIKWID_MARKER_INIT;
LIKWID_MARKER_START("region foo");

foo();

LIKWID_MARKER_STOP("region foo");
LIKWID_MARKER_CLOSE;

运行应用程序:

./likwid-perfctr -g ICACHE -m <your application>

确保编译-DLIKWID-PERFMON并添加 Likwid 包含和库路径并链接 Likwid 库:-L$LIKWID_LIB -I$LIKWID_INCLUDE -llikwid. 一切都在他们的github wiki上有很好的记录

于 2021-02-05T11:37:53.430 回答
1

您可能对gprof(1)感兴趣。它不会测量缓存命中率(这没有任何意义,因为一旦调用GCC并启用了优化,一些调用foo可能会被内联)。

您可以在代码中使用libbacktrace。另请参见time(7)signal(7)

您可以在其中编译代码,gcc -Wall -Wextra -O2 -g -pg然后在其中使用libbacktrace(如GCCRefPerSys正在做的),然后使用gdb(1)编译gprof( 1) 。

通过努力(请阅读Advanced Linux Programming然后阅读syscalls(2)signal-safety(7)),您可以将setitimer(2)sigaction(2)和/或profil(3)一起使用。

还可以考虑生成一些 C 代码(例如,在您自己的 C 代码生成器中使用GPP和/或GNU bison )并查看此答案。J.Pitrat 的书《人造存在:有意识机器的良心》(ISBN-13:978-1848211018)可能会很有启发性。您可能希望生成一些 C 代码以进行额外检测。

您可能会在运行时在插件中生成一些代码(例如使用libgccjitGNU Lightning ...),然后使用dlopen(3)dlsym(3)生成它。阅读有关部分评估的更多信息并查看我的manydl.c示例,更认真地阅读OcamlSBCL的源代码。

你可以编写你的GCC 插件来自动生成一些测量值,比 GCC 的-pg选项更聪明。您的 GCC 插件将(在GIMPLE级别)将大多数函数调用转换为更复杂的东西,从而进行一些基准测试(这是-pgGCC 内部的工作方式,您可以研究 GCC 的源代码)。尝试编译您的foo.casgcc -Wall -Wextra -O2 -pg -S -fverbose-asm foo.c并查看生成的foo.s,也许添加更多优化,或静态分析检测选项。

您可能对ACM SIGPLAN最近的论文感兴趣。

最后,对未经优化编译的 C 程序进行基准测试是没有意义的。考虑改为至少编译和链接你的程序gcc -flto -O2 -Wall

在您的foo中,您可以巧妙地使用clock_gettime(2)来测量 CPU 时间。

如果性能非常重要,并且允许您花费数周的时间来改进它,您可能会考虑使用OpenCL(或者可能是CUDA)在强大的 GPGPU 上计算您的内核。当然,您需要专用硬件。否则,请考虑使用OpenMPOpenACC(或者可能是MPI)。一些最近的 GCC 编译器(至少2020 年10 月的GCC 10 )可以支持这些。当然,请阅读有关Invoking GCC的文档。

于 2020-10-29T06:43:25.867 回答
1

首先,请注意L1-dcache-store-misses您的处理器不支持该功能。perf stat会在输出中告诉你。

perf stat不允许您仅分析选定的代码区域。为此,您必须手动检测代码,以便根据需要控制感兴趣区域周围的指定事件。

如果在处理器 (Haswell) 上不进行多路复用,则无法计算事件L1-dcache-loadsL1-dcache-load-misses和。L1-dcache-stores它们被映射到本地事件MEM_UOPS_RETIRED.ALL_LOADS, L1D.REPLACEMENT, 和MEM_UOPS_RETIRED.ALL_STORES, 分别。这些事件中的每一个只能计数前四个通用计数器。另外,i7-5960X 的规格更新文档中没有记录,但 i7-5960X 中确实存在一个错误(它记录在其他 Haswell 处理器和其他一些微架构的处理器的规格更新文档中)。此错误在不同版本的 perf 中处理方式不同。从内核版本 4.1-rc7 开始,如果在逻辑内核上启用了受 bug 影响的事件之一,并且如果在引导时启用了超线程,则逻辑内核最多只能使用其四个通用内核中的两个计数器。这些MEM_UOPS_RETIRED.*事件是受该错误影响的事件之一。您可以做的一件事是禁用超线程。

了解这些事件可以测量什么样的“缓存命中率”非常重要。您可能不想测量没有意义的东西。一个可能有意义的比率是L1-dcache-load-misses/ ( L1-dcache-loads+ L1-dcache-stores),它表示出于任何原因的 L1D 替换(在缓存中填充导致其他人被驱逐的行)的数量除以退休的加载和存储 uop 的数量。并非所有未命中都会导致替换,并且所有未命中的很大一部分可能会击中 LFB,这也不会导致替换。此外,并非所有替换都是由最终退出的微指令访问引起的。

于 2020-12-02T12:46:56.630 回答