跳转至

H264

描述

目前h264 在6发射 分数为17分,8发射分数为18分

而intel, 龙芯等有20分,需要分析为何又性能差异

h264 是视频编解码测试,主要对系统内存、计算又要求,涉及复杂运算,使用浮点or向量单元,可多核并行。

先看看topdown, IPC =4.3

NoStall 2000万, 其中frag stall 600万,icache stall 200万, 看着比较正常

perf 分析

看看perf

/nfs/home/yanyue/tools/cpu2006v99/benchspec/CPU2006/464.h264ref/run/run_base_test_x86-gcc11.4.0-o3.0000

./h264ref_base.x86-gcc11.4.0-o3 -d foreman_test_encoder_baseline.cfg
运行10s

perf record -g

发现热点代码

核心函数1

接近一半时间在SetupFastFullPelSearch

// max_pos = 1089
for (pos = 0; pos < max_pos; pos++)
    abs_y = offset_y + spiral_search_y[pos];    // 访问稀疏数组
    abs_x = offset_x + spiral_search_x[pos];
    ...
    orgptr = orig_blocks;
  // blky, y 没用,相当于把内层计算执行16次
  for (blky = 0; blky < 4; blky++)
    {
      LineSadBlk0 = LineSadBlk1 = LineSadBlk2 = LineSadBlk3 = 0;
      for (y = 0; y < 4; y++)
      {
          // static pel_t *(*PelYline_11) (pel_t *, int, int, int, int);
          // 这是一个函数指针,FastLine16Y_11, 其中abs_y++
        refptr = PelYline_11 (ref_pic, abs_y++, abs_x, img_height, img_width);
        // 访问内存数据,其中双指针不断++
        LineSadBlk0 += byte_abs [*refptr++ - *orgptr++];
        LineSadBlk0 += byte_abs [*refptr++ - *orgptr++];
        LineSadBlk0 += byte_abs [*refptr++ - *orgptr++];
        // 16个访存,++
      }
      block_sad[bindex++][pos] = LineSadBlk0;
      block_sad[bindex++][pos] = LineSadBlk1;
      block_sad[bindex++][pos] = LineSadBlk2;
      block_sad[bindex++][pos] = LineSadBlk3;
    }
  }

// height=144, width=176
pel_t *FastLine16Y_11 (pel_t *Pic, int y, int x, int height, int width)
{
  return &Pic [y*width+x];
}   // y * 176 + x

在这个三重循环中

p *spiral_search_y@20
$9 = {0, -1, 1, -1, -1, 0, 0, 1, 1, -2, 2, -2, 2, -2, 2, -2, -2, -1, -1, 0}
p *spiral_search_x@20
$12 = {0, 0, 0, -1, 1, -1, 1, -1, 1, -1, -1, 0, 0, 1, 1, -2, 2, -2, 2, -2}
p *ref_pic@20 值比较大
$27 = {50, 216, 255, 238, 255, 255, 255, 252, 248, 255, 253, 252, 252, 253, 255, 243, 252, 253, 253, 254}
p orig_blocks=orgptr
$45 = {43, 216, 255, 249, 251, 255, 254, 253, 251, 252, 255, 255, 255, 254, 254, 253, 48, 198, 193

*refptr = 50 *orgptr = 43
p *byte_abs@20
$30 = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19}

其中核心就是refptr, orgptr 数组的值比较随机化

而byte_abs [refptr++ - orgptr++] 间接访存可能难以预测,但是这和ICache stall 无关啊,这最多算是L2Bound 才对啊,而且byte_abs宽度只有512位,应该不是瓶颈才对

切片793 gem5 数据

看看切片h264ref_foreman.baseline_793/m5out

"h264ref_foreman.baseline": {
        "insts": "525821825911",
        "points": {
            "793": "0.139477",
            "15388": "0.0464417",
            "19006": "0.0133125",
            "369": "0.0950515"
指令占比
IntAlu      10322429     51.14%
MemRead      6645749     32.92%    
MemWrite      1670628      8.28%
    ipc                               3.55

system.cpu.iew.dispatchStallReason::NoStall     22986109                       # Number of dispatch stall reasons each tick (Total) (Count)
system.cpu.iew.dispatchStallReason::IcacheStall      5444280                       # Number of dispatch stall reasons each tick (Total) (Count)
system.cpu.iew.dispatchStallReason::FragStall      7485556                       # Number of dispatch stall reasons each tick (Total) (Count)
system.cpu.iew.dispatchStallReason::MemDQBandwidth      2516134
system.cpu.iew.dispatchStallReason::CommitSquash      2889448                       # Number of dispatch stall reasons each tick (Total) (Count)
system.cpu.iew.dispatchStallReason::total     43696680
system.cpu.iew.branchMispredicts                36660

看这个里面主要的几个还是icache Stall, 计算瓶颈没有太大,还有MemDQBandwidth 也有一些

核心函数2

看看PartitionMotionSearch 函数(另一个SubPelBlockMotionSearch 核心循环还是计算指令)

//===== loop over all search positions =====
  for (pos=0; pos<max_pos; pos++, block_sad++)
  {
    if (*block_sad < min_mcost)
    {
      cand_x = offset_x + spiral_search_x[pos];
        // ***
    }
  }
 if (*block_sad < min_mcost)                                                                                                                                                                          
 15.63 21a8:   mov       (%r12,%rax,4),%esi                                                                                                                                                                       
 16.02         cmp       %r13d,%esi                                                                                                                                                                               
              jge       21f6                                                                                                                                                                                     
             cand_x = offset_x + spiral_search_x[pos];                                                                                                                                                            
  2.38         mov       (%r10,%rax,4),%ecx                                                                                                                                                                       
             cand_y = offset_y + spiral_search_y[pos];                                                                                                                                                            
        // 。。。                                                                                                                                                                        ▒
             for (pos=0; pos<max_pos; pos++, block_sad++)                                                                                                                                                         
 19.81 21f6:   add       $0x1,%rax                                                                                                                                                                                
 18.40         cmp       %rdi,%rax                                                                                                                                                                                
              jne       21a8         

发现这个函数核心开销还是一个小循环,然后还得不断if 判断,很可能不进入if 条件内,而是不断i++,这里很可能导致前端的fragment, 或者是two-taken 才能缓解。

切片793核心循环

最后验证一下h264_baseline 中793 和核心循环是什么, 使用commitTrace + python 脚本分析得到

Top 10 most common basic blocks:
Count: 305728
Instructions:
  0x67588: c_lw a0, 0(a3)
  0x6758a: c_addi a3, 4
  0x6758c: bge a0, s5, 74

Count: 305323
Instructions:
  0x675d6: c_addiw a4, 1
  0x675d8: c_addi a1, 4
  0x675da: c_addi a2, 4
  0x675dc: bne s2, a4, -84

刚好对应PartitionMotionSearch中的for 循环, 下面是objdump

   67588:   4288                    lw  a0,0(a3)        // if 比较
   6758a:   0691                    addi    a3,a3,4
   6758c:   05555563            bge a0,s5,675d6 <PartitionMotionSearch+0x192c>

   675d6:   2705                    addiw   a4,a4,1     // for  i++
   675d8:   0591                    addi    a1,a1,4
   675da:   0611                    addi    a2,a2,4
   675dc:   fae916e3            bne s2,a4,67588 <PartitionMotionSearch+0x18de>

369切片

system.cpu.ipc                               3.765852

system.cpu.scheduler.IQ_ldu.full                30949                       # count of iq full (Count)
system.cpu.scheduler.IQ_ldu.bwfull            3853877 

system.cpu.iew.dispatchedInsts               21391321                       # Number of instructions dispatched to IQ (Count)
system.cpu.iew.dispSquashedInsts                    0                       # Number of squashed instructions skipped by dispatch (Count)
system.cpu.iew.dispLoadInsts                  7638219                       # Number of dispatched load instructions (Count)
system.cpu.iew.dispStoreInsts                 1453510

system.cpu.iew.iqFullEvents                   3896719

system.cpu.iew.dispatchStallReason::MemDQBandwidth      7228268

system.cpu.commit.committedInstType_0::IntAlu     10461949     52.31%     52.31%
system.cpu.commit.committedInstType_0::MemRead      7389594     36.95%

这里的MemDQBandwidth 较高

并且load指令很高,可能是访存bound

核心循环, 长达100条指令的一个基本块!对应SetupFastFullPelSearch 函数

Count: 87120        
Instructions:
  0x64106: lhu t6, 0(a0)
  0x6410a: lhu s11, -32(s0)
  0x6410e: lhu t3, 4(a0)

  0x64162: subw a6, a6, s6
  0x64166: sh2add t6, t6, a5

  0x6427e: addw t6, t6, t3
  0x64282: addw s2, a6, a3
  0x64286: addw s3, t6, t1
  0x6428a: beq s4, a5, 66

其中主要是load, store, sub, sh2add, add 这四类指令

        IssueQue(name='IQ_stu', inoutPorts=2, size=2*24, fuType=[WritePort()]),
        IssueQue(name='IQ_ldu', inoutPorts=2, size=2*24, fuType=[ReadPort()]),

        IssueQue(name='IQ_stu', inoutPorts=3, size=3*16, fuType=[WritePort()]),
        IssueQue(name='IQ_ldu', inoutPorts=3, size=3*16, fuType=[ReadPort()]),

神奇,把发射队列宽度更改后,RTL那边是1个Load IQ + 1 Store IQ + 1 Ld/St IQ, 每个能发射两条指令,对应最多能发射3个Load指令

改了之后,ipc 从3.76 提升到4.181760, 提升了10%!

system.cpu.iew.dispatchStallReason::MemDQBandwidth 8010 下降非常多!

score:

h264ref 382.775 22130.0 19.272 1.0

h264 的分数也从17.63分(6发射), 18.34分(8发射)提升到19.27分,至少目前gem5和RTL 分数比较一致了!

结论

目前h264 的计算、访存等并不存在瓶颈

还是在取指令模块,由于for循环内不断有if判断,导致取指带宽不足

需要two taken 来解决!

解决后,ipc 能从3.5到6, 分数从(15->25 ) * 13% = 1.3分,预估能带来1分多的提升。

此外,修改成3个Load IQ 也能带来1分的性能提升。