• pcm.x代码分析


    简介

    运行说明

    pcm 监控结果可以分为核心、socket 和系统三部分。在核心监控部分,结果包括如下内容:
    • EXEC
    • IPC:每 CPU 周期指令数
    • FREQ:普通CPU频率系数
    • AFREQ:激活状态普通CPU频率系数
    • L3MISS:L3(读)缓存miss
    • L2MISS:L2(读)缓存miss
    • L3HIT:L3(读)缓存命中率(0.00-1.00)
    • L2HIT:L2(读)缓存命中率(0.00-1.00)
    • L3MPI:每周期L3(读)缓存miss数
    • L2MPI:每周期L2(读)缓存miss数
    • L3OCC:L3占用比率
    • TEMP:温度
    • energy:
    此外,在核心端还提供了每个核心C-Stat状态(C0-C6)等。

    在socket端,监控结果中提供了UPI带宽/利用率,和内存读写大小相关内容,包括:
    • READ:从主内存控制器读取字节数
    • WRITE:从主内存控制器写入字节数
    • LOCAL:本地内存读取百分比
    • PMM RD:从 PMM 内存读取字节数
    • PMM WR:从 PMM 内存写入字节数

    运行分析

    整体运行流程

    下面介绍函数整体运行流程。按照INTEL性能分析流程,需要首先在事件选择寄存器中定义监控事件,随后读取监控寄存器值进行取样。为了解pcm.x运行过程中监控事件,首先查找其对监控事件定义函数。

    缓存事件定义

    在 pcm.cpp 中,main函数执行时会首先分析命令行中输入参数,随后对相应参数进行设置,随后在1275 行处,调用如下函数对监控事件进行了编写。

        // program() creates common semaphore for the singleton, so ideally to be called before any other references to PCM
        PCM::ErrorCode status = m->program();
    

    其中 program() 方法的定义格式为

    ErrorCode program(
          const ProgramMode mode_ = DEFAULT_EVENTS,
          const void *parameter_ = NULL); // program counters and start counting
    

    在此函数中,参数 mode_ 类型为枚举类型 ProgramMode,其选择包括 DEFAULT_EVENTS,CUSTOM_CORE_EVENTS,EXT_CUSTOM_CORE_EVENTS 和 INVALID_MODE 四种。当没有输入参数时,program() 将采用默认参数 DEFAULT_EVENTS 进行调用。查看代码中对应此模块的监控事件及掩码定义为

    switch (cpu_model)
          {
          case SKL:
          case SKX:
          case KBL:
            assert(useSkylakeEvents());
            coreEventDesc[0].event_number = SKL_MEM_LOAD_RETIRED_L3_MISS_EVTNR;
            coreEventDesc[0].umask_value = SKL_MEM_LOAD_RETIRED_L3_MISS_UMASK;
            coreEventDesc[1].event_number = SKL_MEM_LOAD_RETIRED_L3_HIT_EVTNR;
            coreEventDesc[1].umask_value = SKL_MEM_LOAD_RETIRED_L3_HIT_UMASK;
            coreEventDesc[2].event_number = SKL_MEM_LOAD_RETIRED_L2_MISS_EVTNR;
            coreEventDesc[2].umask_value = SKL_MEM_LOAD_RETIRED_L2_MISS_UMASK;
            coreEventDesc[3].event_number = SKL_MEM_LOAD_RETIRED_L2_HIT_EVTNR;
            coreEventDesc[3].umask_value = SKL_MEM_LOAD_RETIRED_L2_HIT_UMASK;
    

    其中 SKL_MEM_LOAD_RETIRED_L3_MISS_EVTNR 与 SKL_MEM_LOAD_RETIRED_L3_MISS_UMASK 等都为宏变量,通过查找Intel手册查找对应的监控事件为

    事件 事件编号 掩码 掩码编号 说明
    SKL_MEM_LOAD_RETIRED_L3_MISS_EVTNR 0xD1 SKL_MEM_LOAD_RETIRED_L3_MISS_UMASK 0x20 Counts retired load instructions with at least one uop that missed in the L3 cache.
    SKL_MEM_LOAD_RETIRED_L3_HIT_EVTNR 0xD1 SKL_MEM_LOAD_RETIRED_L3_HIT_UMASK 0x04 Counts retired load instructions with at least one uop that hit in the L3 cache.
    SKL_MEM_LOAD_RETIRED_L2_MISS_EVTNR 0xD1 SKL_MEM_LOAD_RETIRED_L2_MISS_UMASK 0x10 Retired load instructions missed L2 cache as data sources.
    SKL_MEM_LOAD_RETIRED_L2_HIT_EVTNR 0xD1 SKL_MEM_LOAD_RETIRED_L2_HIT_UMASK 0x02 Retired load instructions with L2 cache hits as data sources.

    通过使用核心中四个通用函数寄存器以上事件的监控,即可获得对应时间监控结果。

    缓存事件输出

    在了解定义监控事件后,跳过中间监控过程分析,首先分析对监控事件结果的分析,了解最终输出使用的结果变量。在 pcm.x 中,结果输出位于第 1366 行,包括 csv 格式和命令行格式输出。

            if (csv_output)
                print_csv(m, cstates1, cstates2, sktstate1, sktstate2, ycores, sstate1, sstate2, cpu_model, show_core_output, show_partial_core_output, show_socket_output, show_system_output);
            else
                print_output(m, cstates1, cstates2, sktstate1, sktstate2, ycores, sstate1, sstate2, cpu_model, show_core_output, show_partial_core_output, show_socket_output, show_system_output);
    

    下面主要查看非 csv 格式输出时对应结果。在输出过程中,所有核心结果显示在下列代码中

        if (show_core_output)
        {
            for (uint32 i = 0; i < m->getNumCores(); ++i)
            {
                if (m->isCoreOnline(i) == false || (show_partial_core_output && ycores.test(i) == false))
                    continue;
                if (cpu_model == PCM::KNL)
                    cout << setfill(' ') << internal << setw(5) << i
                    << setw(5) << m->getTileId(i) << setw(5) << m->getCoreId(i)
                    << setw(7) << m->getThreadId(i);
                else
                    cout << " " << setw(3) << i << "   " << setw(2) << m->getSocketId(i);
                print_basic_metrics(m, cstates1[i], cstates2[i]);
                print_other_metrics(m, cstates1[i], cstates2[i]);
            }
        }
    

    其中包含了 print_basic_metrics()print_other_metrics() 两个不同过程,分别对缓存命中率相关指标,缓存占用和内存带宽等指标进行了监控。在基本指标中,缓存事件结果输出对应代码如下

    template <class State>
    void print_basic_metrics(const PCM * m, const State & state1, const State & state2)
    {
        cout << "     " << getExecUsage(state1, state2) <<
            "   " << getIPC(state1, state2) <<
            "   " << getRelativeFrequency(state1, state2);
        if (m->isActiveRelativeFrequencyAvailable())
            cout << "    " << getActiveRelativeFrequency(state1, state2);
        if (m->isL3CacheMissesAvailable())
            cout << "    " << unit_format(getL3CacheMisses(state1, state2));
        if (m->isL2CacheMissesAvailable())
            cout << "   " << unit_format(getL2CacheMisses(state1, state2));
        if (m->isL3CacheHitRatioAvailable())
            cout << "    " << getL3CacheHitRatio(state1, state2);
        if (m->isL2CacheHitRatioAvailable())
            cout << "    " << getL2CacheHitRatio(state1, state2);
        if (m->isL3CacheMissesAvailable())
            cout << "    " << double(getL3CacheMisses(state1, state2)) / getInstructionsRetired(state1, state2);
        if (m->isL2CacheMissesAvailable())
            cout << "    " << double(getL2CacheMisses(state1, state2)) / getInstructionsRetired(state1, state2);
    }
    

    可以看到,L2/L3 Miss 与命中率等指标都在函数 getL3CacheMisses() 和 getL3CacheHitRatio() 等过程中计算。进一步查看对应代码

    template <class CounterStateType>
    uint64 getL3CacheMisses(const CounterStateType &before,
                            const CounterStateType &after)
    {
      if (!PCM::getInstance()->isL3CacheMissesAvailable())
        return 0;
      return after.L3Miss - before.L3Miss;
    }
    

    可以了解到,在函数中输入的 before 和 after 分别代表前后两步的监控结果,而 L3Miss 则为监控结果。对应的在计算缓存命中率时,对应的函数过程为

    template <class CounterStateType>
    double getL3CacheHitRatio(const CounterStateType &before,
                              const CounterStateType &after) // 0.0 - 1.0
    {
      if (!PCM::getInstance()->isL3CacheHitRatioAvailable())
        return 0;
      const auto hits = getL3CacheHits(before, after);
      const auto misses = getL3CacheMisses(before, after);
      return double(hits) / double(hits + misses);
    }
    

    其中计算缓存命中公式为 hits/(hits + misses) ,而命中事件又由两部分组成,分别为

    template <class CounterStateType>
    uint64 getL3CacheHits(const CounterStateType &before,
                          const CounterStateType &after)
    {
      if (!PCM::getInstance()->isL3CacheHitsAvailable())
        return 0;
      return getL3CacheHitsSnoop(before, after) +
             getL3CacheHitsNoSnoop(before, after);
    }
    

    getL3CacheHitsSnoop()getL3CacheHitsNoSnoop() 中分别调用 after.SKLL3Hit 和 after.L3UnsharedHit 进行计算。

    缓存事件监控

    在定义监控事件后,下一步就是读取对应监控寄存器结果,并按照对应定义计算结果。在 pcm.x 监控过程中,使用如下代码实现事件监控功能。

       m->getAllCounterStates(sstate1, sktstate1, cstates1);
    

    但是在整个函数运行过程中,并没有找到对应变量 before.L3Miss 或 after.SKLL3Hit/after.L3UnsharedHit 等的赋值过程。

    从头查看三个相关属性的定义,在头文件 cpucounters.h 中可以看到,相关变量采用的是联合体进行定义

      union  {
        uint64 L3Miss;
        uint64 Event0;
        uint64 ArchLLCMiss;
      };
      union  {
        uint64 L3UnsharedHit;
        uint64 Event1;
        uint64 ArchLLCRef;
        uint64 SKLL3Hit;
      };
      union  {
        uint64 L2HitM;
        uint64 Event2;
        uint64 SKLL2Miss;
      };
      union  {
        uint64 L2Hit;
        uint64 Event3;
      };
    

    所谓联合体定义就是花括号内所有变量起始地址都完全相同,即在同一块内存地址区域内使用了多个变量名。从这就可以看出,在缓存监控中使用的四个事件监控寄存器对应变量分别为

    • PCM0:L3Miss
    • PCM1:L3UnsharedHit
    • PCM2:L2HitM
    • PCM3:L2Hit

    对应计算不同层级缓存Miss和命中率公式为

    • L3 Cache Miss = L3Miss
    • L2 Cache Miss = L2HitM + L3UnsharedHit + L3Miss
    • L3 Cache Hits = L3UnsharedHit + L2HitM
    • L2 Cache Hits = L2Hit
    • L3 Cache Hit Ratio = L3 Cache Hit /(L3 Cache Miss + L3 Cache Hits)
    • L2 Cache Hit Ratio = L2 Cache Hit /(L2 Cache Miss + L2 Cache Hits)

    通过以上公式,即可计算出对应 L2 和 L3 缓存对应 Miss 和命中率大小。

    总结

    本文对 pcm.x 代码运行过程进行了分析,考察了对缓存进行监控时需要采用事件和掩码编号,最后对结果输出过程中,缓存命中率计算公式进行了分析。

  • 相关阅读:
    safeNet
    网店
    微信公众号自定义菜单与回车
    西游记对教育的启发
    zencart资源
    cmd批处理常用符号详解
    div垂直居中
    git工作量统计
    VS2012变化的快捷键:
    sql 树 递归
  • 原文地址:https://www.cnblogs.com/li12242/p/14155271.html
Copyright © 2020-2023  润新知