【问题标题】:Cannot understand the metric returned by "perf" regarding the cache-misses无法理解“perf”返回的关于缓存未命中的指标
【发布时间】:2017-04-19 23:03:17
【问题描述】:

我的问题是关于了解 Linux perf 工具指标。我在我的代码中做了一个与预取/缓存未命中相关的优化,现在速度更快了。但是,性能并没有向我展示 (或者更确切地说,我不明白 perf 向我展示了什么)

把它带回到一切开始的地方。我为了speed up random memory access using prefetch做了调查。

这是我的程序的作用:

  1. 它使用两个大小相同的 int 缓冲区
  2. 它一个接一个地读取第一个缓冲区的所有值
    • 每个值都是第二个缓冲区中的随机索引
  3. 它读取第二个缓冲区中索引处的值
  4. 它将从第二个缓冲区获取的所有值相加
  5. 它完成了之前的所有步骤,越来越大
  6. 最后,我打印自愿和非自愿 CPU 上下文切换的次数

最后一次调优后,我的代码如下:

#define _GNU_SOURCE

#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
#include <sys/time.h>
#include <math.h>
#include <sched.h>

#define BUFFER_SIZE ((unsigned long) 4096 * 50000)
#define PADDING     256

unsigned int randomUint()
{
  int value = rand() % UINT_MAX;
  return value;
}


unsigned int * createValueBuffer()
{
  unsigned int * valueBuffer = (unsigned int *) malloc(BUFFER_SIZE * sizeof(unsigned int));
  for (unsigned long i = 0 ; i < BUFFER_SIZE ; i++)
  {
    valueBuffer[i] = randomUint();
  }

  return (valueBuffer);
}


unsigned int * createIndexBuffer()
{
  unsigned int * indexBuffer = (unsigned int *) malloc((BUFFER_SIZE + PADDING) * sizeof(unsigned int));
  for (unsigned long i = 0 ; i < BUFFER_SIZE ; i++)
  {
    indexBuffer[i] = rand() % BUFFER_SIZE;
  }

  return (indexBuffer);
}


double computeSum(unsigned int * indexBuffer, unsigned int * valueBuffer, unsigned short prefetchStep)
{
  double sum = 0;

  for (unsigned int i = 0 ; i < BUFFER_SIZE ; i++)
  {
    __builtin_prefetch((char *) &valueBuffer[indexBuffer[i + prefetchStep]], 0, 0);
    unsigned int index = indexBuffer[i];
    unsigned int value = valueBuffer[index];
    double s = sin(value);
    sum += s;
  }

  return (sum);
}


unsigned int computeTimeInMicroSeconds(unsigned short prefetchStep)
{
  unsigned int * valueBuffer = createValueBuffer();
  unsigned int * indexBuffer = createIndexBuffer();

  struct timeval startTime, endTime;
  gettimeofday(&startTime, NULL);

  double sum = computeSum(indexBuffer, valueBuffer, prefetchStep);

  gettimeofday(&endTime, NULL);

  printf("prefetchStep = %d, Sum = %f - ", prefetchStep, sum);
  free(indexBuffer);
  free(valueBuffer);

  return ((endTime.tv_sec - startTime.tv_sec) * 1000 * 1000) + (endTime.tv_usec - startTime.tv_usec);

}


void testWithPrefetchStep(unsigned short prefetchStep)
{
    unsigned int timeInMicroSeconds = computeTimeInMicroSeconds(prefetchStep);
    printf("Time: %u micro-seconds = %.3f seconds\n", timeInMicroSeconds, (double) timeInMicroSeconds / (1000 * 1000));

}


int iterateOnPrefetchSteps()
{
  printf("sizeof buffers = %ldMb\n", BUFFER_SIZE * sizeof(unsigned int) / (1024 * 1024));
  for (unsigned short prefetchStep = 0 ; prefetchStep < 250 ; prefetchStep++)
  {
    testWithPrefetchStep(prefetchStep);
  }
}

void setCpuAffinity(int cpuId)
{
  int pid=0;
  cpu_set_t mask;
  unsigned int len = sizeof(mask);
  CPU_ZERO(&mask);
  CPU_SET(cpuId,&mask);
  sched_setaffinity(pid, len, &mask);
}

int main(int argc, char ** argv)
{
  setCpuAffinity(7);

  if (argc == 2)
  {
    testWithPrefetchStep(atoi(argv[1]));
  }
  else
  {
    iterateOnPrefetchSteps();
  }

}

在我之前的 stackoverflow 问题结束时,我认为我拥有所有元素:为了避免缓存未命中,我让我的代码预取数据(使用 __builtin_prefetch)并且我的程序更快.一切看起来都尽可能正常

但是,我想使用 Linux perf 工具来研究它。所以我对我的程序的两次执行进行了比较:

  • ./TestPrefetch 0:这样做,预取是低效的,因为它是在刚刚读取的数据上完成的(当数据被访问时,它不能被加载到 CPU 缓存中)。运行时长:21.346 秒
  • ./TestPrefetch 1:这里的预取效率要高得多,因为在读取数据之前会先进行一次循环迭代。运行时长:12.624 秒

perf 输出如下:

$ gcc -O3 TestPrefetch.c -o TestPrefetch -lm  && for cpt in 0 1; do echo ; echo "### Step=$cpt" ; sudo perf stat -e task-clock,cycles,instructions,cache-references,cache-misses ./TestPrefetch $cpt; done  

### Step=0
prefetchStep = 0, Sum = -1107.523504 - Time: 21346278 micro-seconds = 21.346 seconds

 Performance counter stats for './TestPrefetch 0':

      24387,010283      task-clock (msec)         #    1,000 CPUs utilized          
    97 274 163 155      cycles                    #    3,989 GHz                    
    59 183 107 508      instructions              #    0,61  insn per cycle         
       425 300 823      cache-references          #   17,440 M/sec                  
       249 261 530      cache-misses              #   58,608 % of all cache refs    

      24,387790203 seconds time elapsed


### Step=1
prefetchStep = 1, Sum = -1107.523504 - Time: 12623665 micro-seconds = 12.624 seconds

 Performance counter stats for './TestPrefetch 1':

      15662,864719      task-clock (msec)         #    1,000 CPUs utilized          
    62 159 134 934      cycles                    #    3,969 GHz                    
    59 167 595 107      instructions              #    0,95  insn per cycle         
       484 882 084      cache-references          #   30,957 M/sec                  
       321 873 952      cache-misses              #   66,382 % of all cache refs    

      15,663437848 seconds time elapsed

在这里,我很难理解为什么我更好:

  • cache-misses 的数量几乎相同(我什至还有一点点):我不明白为什么,(总体而言)如果是这样,为什么我更快?
  • cache-references 是什么?
  • 什么是任务时钟和周期?它们是否包括在缓存未命中时等待数据访问的时间?

【问题讨论】:

    标签: performance caching cpu-cache perf prefetch


    【解决方案1】:

    不明白为什么,(总体而言)如果是这样,为什么我的速度更快?

    因为您每次运行的指令更多。旧的:

    0,61  insn per cycle 
    

    还有新的

    0,95  insn per cycle
    

    什么是缓存引用?

    计算缓存是否包含您正在加载/存储的数据被询问的次数。

    什么是任务时钟和周期?它们是否包括在缓存未命中的情况下等待数据访问的时间?

    是的。但请注意,在今天的处理器中,没有任何等待。指令是乱序执行的,通常是预取的,如果下一条指令需要一些尚未准备好的数据,则将执行其他指令。

    【讨论】:

    • insn per cycle 是所有执行的平均值,不是吗?我的程序现在更快(12.624 秒对 21.346 秒)所以逻辑上insn per cycle 更高。但我仍然不明白为什么cache-misses 更高
    • 其实我还是不明白为什么cache-misses更高
    • “指令被乱序执行” 你到底是什么意思? “如果下一条指令需要一些尚未准备好的数据,其他指令将被执行” 我不明白你的意思?你能给我提供例子或好的链接吗?谢谢
    • cache-misses 更高,因为预取可能算作缓存未命中。它更高,但您显然会看到结果。
    • 基本上Intel Hyper Threading是一种OOO执行。
    【解决方案2】:

    我不相信性能摘要,因为不太清楚每个名称代表什么以及它们编程遵循的性能计数器。已知默认设置也会计算错误的东西(参见 - https://software.intel.com/en-us/forums/software-tuning-performance-optimization-platform-monitoring/topic/557604

    这里可能发生的情况是,您的缓存未命中计数器也可能计算预取指令(这可能看起来像是机器的负载,尤其是当您在缓存层次结构中下降时)。在这种情况下,拥有更多的缓存引用(查找)是有意义的,并且您会期望这些请求是未命中的(预取的全部意义就是未命中......)。

    不要依赖一些模棱两可的计数器,而是为您的特定机器找到我们的计数器 ID 和掩码,它们代表 需求读取查找和未命中,看看它们是否有所改进。

    编辑:再次查看您的数字,我发现访问量增加了约 5000 万次,但未命中约 7000 万次。由于预取完成的缓存抖动,可能会导致更多未命中

    【讨论】:

      【解决方案3】:

      我最近在性能问题上取得了进展。我发现了很多新事件,其中一些非常有趣。

      关于当前的问题,必须考虑以下事件:L1-icache-load-misses

      当我在与以前相同的条件下使用 perf 监控我的测试应用程序时,我得到了此事件的以下值:

           1 202 210      L1-icache-load-misses
      

      反对

             530 127      L1-icache-load-misses
      

      目前,我还不明白为什么 cache-misses 事件不受预取影响,而 L1-icache-load-misses 是...

      【讨论】:

        猜你喜欢
        • 2017-10-03
        • 2015-07-05
        • 2014-07-28
        • 2021-07-10
        • 2013-01-18
        • 2014-06-29
        • 2014-06-13
        • 2019-07-28
        • 1970-01-01
        相关资源
        最近更新 更多