【问题标题】:Optimizing Cortex-A8 color conversion using NEON使用 NEON 优化 Cortex-A8 颜色转换
【发布时间】:2014-03-03 12:19:39
【问题描述】:

我目前正在执行一个颜色转换例程,以便从 YUY2 转换为 NV12。 我有一个非常快的函数,但没有我预期的那么快,主要是由于缓存未命中。

void convert_hd(uint8_t *orig, uint8_t *result) {
uint32_t width          = 1280;
uint32_t height         = 720;
uint8_t *lineOdd        = orig;
uint8_t *lineEven       = orig + width*2;
uint8_t *resultYOdd     = result;
uint8_t *resultYEven    = result + width;
uint8_t *resultUV       = result + height*width;
uint32_t totalLoop      = height/2;

while (totalLoop-- > 0) {
  uint32_t lineLoop = 1280/32; // Bytes length: width*2, read by iter 16Bytes

  while(lineLoop-- > 0) {
    __asm__ __volatile__(
        "pld [%[lineOdd]]   \n\t"
        "vld4.8   {d0, d1, d2, d3}, [%[lineOdd],:128]!   \n\t" // d0:Y d1:U0 d2:Y d3:V0
        "pld [%[lineEven]]   \n\t"
        "vld4.8   {d4, d5, d6, d7}, [%[lineOdd],:128]!   \n\t" // d4:Y d5:U1 d6:Y d7:V1
        "vld4.8   {d8, d9, d10, d11}, [%[lineEven],:128]!  \n\t" // d8:Y d9:U0' d10:Y d11:V0'
        "vld4.8   {d12, d13, d14, d15}, [%[lineEven],:128]!  \n\t" // d12:Y d13:U1' d14:Y d15:V1'
        "vhadd.u8   d1, d1, d9    \n\t" // (U0+U0') / 2
        "vhadd.u8   d3, d3, d11    \n\t" // (V0+V0') / 2
        "vhadd.u8   d5, d5, d13    \n\t" // (U1+U1') / 2
        "vhadd.u8   d7, d7, d15    \n\t" // (V1+V1') / 2
        // Save
        "vst2.8 {d0, d2}, [%[resultYOdd],:128]!           \n\t"
        "vst2.8 {d4, d6}, [%[resultYOdd],:128]!           \n\t"
        "vst2.8 {d8, d10}, [%[resultYEven],:128]!          \n\t"
        "vst2.8 {d12, d14}, [%[resultYEven],:128]!          \n\t"
        "vst2.8 {d1, d3}, [%[resultUV],:128]!   \n\t"
        "vst2.8 {d5, d7}, [%[resultUV],:128]!   \n\t"
        : [lineOdd]"+r"(lineOdd), [lineEven]"+r"(lineEven), [resultYOdd]"+r"(resultYOdd), [resultYEven]"+r"(resultYEven), [resultUV]"+r"(resultUV)
        :
        : "memory"
    );
  }
  lineOdd += width*2;
  lineEven += width*2;
  resultYOdd += width;
  resultYEven += width;
}
}

当我问 oprofile 什么需要时间时,它会说:

                                           :    220c:   add r2, r0, #2560   ;
                                           :    2210:   add r3, r1, #1280   ;
                                           :    2214:   add ip, r1, #921600 ;
                                           :    2218:   push    {r4, lr}
                                           :    221c:   mov r4, #360    ;
 6  0.1243    10  0.5787     4  0.4561     :    2220:   mov lr, #40 ; 0x28
 9  0.1864     5  0.2894     0       0     :    2224:   pld [r0]
45  0.9321     7  0.4051     3  0.3421     :    2228:   vld4.8  {d0-d3}, [r0 :128]!
51  1.0563     7  0.4051     1  0.1140     :    222c:   pld [r2]
 1  0.0207     1  0.0579     0       0     :    2230:   vld4.8  {d4-d7}, [r0 :128]!
1360 28.1690   770 44.5602   463 52.7936     :    2234: vld4.8  {d8-d11}, [r2 :128]!
 980 20.2983   329 19.0394   254 28.9624     :    2238: vld4.8  {d12-d15}, [r2 :128]!
                                             :    223c: vhadd.u8    d1, d1, d9
1000 20.7125   170  9.8380   104 11.8586     :    2240: vhadd.u8    d3, d3, d11
                                             :    2244: vhadd.u8    d5, d5, d13
   5  0.1036     2  0.1157     2  0.2281     :    2248: vhadd.u8    d7, d7, d15
                                             :    224c: vst2.8  {d0,d2}, [r1 :128]!
1125 23.3016   293 16.9560    15  1.7104     :    2250: vst2.8  {d4,d6}, [r1 :128]!
  34  0.7042    41  2.3727     0       0     :    2254: vst2.8  {d8,d10}, [r3 :128]!
  74  1.5327     8  0.4630     0       0     :    2258: vst2.8  {d12,d14}, [r3 :128]!
  60  1.2428    39  2.2569     6  0.6842     :    225c: vst2.8  {d1,d3}, [ip :128]!
  53  1.0978    24  1.3889    14  1.5964     :    2260: vst2.8  {d5,d7}, [ip :128]!
                                             :    2264: subs    lr, lr, #1
   0       0     0       0     1  0.1140     :    2268: bne 2224 <convert_hd+0x18>
  11  0.2278    14  0.8102    10  1.1403     :    226c: subs    r4, r4, #1
                                             :    2270: add r0, r0, #2560   ;
                                             :    2274: add r2, r2, #2560   ;
   2  0.0414     6  0.3472     0       0     :    2278: add r1, r1, #1280   ;
                                             :    227c: add r3, r3, #1280   ;
   2  0.0414     1  0.0579     0       0     :    2280: bne 2220 <convert_hd+0x14>
                                             :    2284: pop {r4, pc}
  • 前两列是循环计数(绝对和相对)
  • 接下来的两个是 L1 缓存未命中(绝对和相对)
  • 最后一个是 L2 缓存未命中(绝对和相对)

任何帮助将不胜感激,因为目前这是一项非常困难的任务,要找出想法并避免缓存未命中...

谢谢!

【问题讨论】:

  • 看起来可以改进的一件事是您对缓存预加载的使用。通过使用 PLD [R2],您是在告诉系统预加载您将立即从中读取的内存。您需要做的是告诉它在您正在阅读的位置之前预加载数据,以便在您需要时准备好它(例如 PLD [R2,#0x200])。
  • @BitBank 我也试过了,但在 r2 (lineEven) 上仍然有很多缓存未命中。
  • 没有办法避免缓存未命中,因为 CPU 比内存快得多。通过优化 R0 和 R2 的预加载距离,您可以最大限度地减少失误。尝试不同的距离,直到找到最佳距离。这通常可以加快 20-25% 的速度。
  • 其他可以加快速度的方法是再次展开循环。你只使用了 NEON 寄存器的 1/2。
  • 您无法避免缓存未命中。您可以提高缓存未命中率。它描述了您要实现的目标。如果您真的喜欢它,请从删除 PLD 开始,将循环变成单个循环,确保 *origin、*result 与高速缓存行大小对齐,交错加载和存储,同时执行时序并找到最佳布局,然后放入一些 PLD。 PLD 应该始终领先于您当前正在做的事情,但不远。如果这不是 Cortex-A8,我会说完全放弃它,因为在不同内核上通用/广泛使用时很难做到正确。

标签: assembly arm neon cpu-cache cortex-a8


【解决方案1】:

高速缓存行长度固定为 8 个字(32 字节)。除了您当前拥有的pld,您还需要pld[lineEven+cacheLine]。未命中的是vld4.8 {d8-d11},这是lineEven 的第二半。 pld 只会获取一个缓存行。此外,您应该更改pld 位置。将一个放在开头,另一个放在vhadd 之前,可能是下一个内存目标。然后,您可以并行激活 ALU 和内存单元。

另外,将vst2.8 {d0, d2}vhadd 交错;看起来大多数数据都是内存传输。 vhadd 将阻止数据依赖关系,例如 d9,您可能/可能没有从 pld 加载,但未安排好。

我对 NEON 不是很熟悉,但以下是我所说的尝试。

__asm__ __volatile__(
    "pld [%[lineOdd], #32]\n\t" // 2nd part of odd.
    "vld4.8   {d0, d1, d2, d3}, [%[lineOdd],:128]!\n\t"
    "pld [%[lineEven], #32]\n\t" // 2nd part of even.
    "vld4.8   {d8, d9, d10, d11}, [%[lineEven],:128]!\n\t"
    "vld4.8   {d4, d5, d6, d7}, [%[lineOdd],:128]!\n\t"
    "vld4.8   {d12, d13, d14, d15}, [%[lineEven],:128]!\n\t" 
    "vhadd.u8   d1, d1, d9\n\t"
    // First in memory pipe, so write early.
    "vst2.8 {d0, d2}, [%[resultYOdd],:128]!\n\t"  
    "vhadd.u8   d3, d3, d11\n\t"
    "vst2.8 {d8, d10}, [%[resultYEven],:128]!\n\t"
    "vhadd.u8   d5, d5, d13\n\t"
    "vst2.8 {d4, d6}, [%[resultYOdd],:128]!           \n\t"
    "vhadd.u8   d7, d7, d15\n\t"
    "vst2.8 {d12, d14}, [%[resultYEven],:128]!          \n\t"
    "pld [%[lineOdd]]\n\t"   // 1st part of odd.
    "vst2.8 {d1, d3}, [%[resultUV],:128]!   \n\t"
    "pld [%[lineEven]]\n\t"  // 1st part of even.
    "vst2.8 {d5, d7}, [%[resultUV],:128]!   \n\t"
    : [lineOdd]"+r"(lineOdd), [lineEven]"+r"(lineEven),
      [resultYOdd]"+r"(resultYOdd), [resultYEven]"+r"(resultYEven),
      [resultUV]"+r"(resultUV)
    :
    : "memory"
);

我可能有错误的是 NEON 操作的步幅;我不知道你的寄存器有多宽(64/128),所以可能需要更多的PLD,等等。最好将存储操作与添加交错。特别是,一些dX 将在其他人之前加载,它们将可以使用。否则,您的 ALU (vhadd) 将阻塞等待数据加载。

您可能还希望在开始之前用pld[lineOdd]pld[lineEven] 准备循环。

【讨论】:

  • 预加载距离与缓存行长度无关;它是关于系统需要多长时间来读取内存,以便在您的代码需要时准备好。
  • PLD for Cortex-A8PLD for Cortex-A5;两者都在谈论缓存和缓存行。此外,OP 提供的数据支持第二半缺失的事实。另请参阅 Linux copy template 及其 PLD 值。
  • True - 需要发出多个预加载指令来加载正在处理的数量。我想我误解了你的答案,暗示它应该是缓存行长度。
  • 我刚刚尝试了你的代码,@artlessnoise,分析的结果在这里:pastebin.com/T79H2Sfm 如你所见,它不是很好(事实上,时机也不好)。
  • @jmh 将vld4.8 {d4-d7}vld4.8 {d12-d15} 向下移动到vhadd.u8 d5, d5, d13;这些是缓存未命中,在此之前您不需要数据。我没有说我的代码会更好;你肯定需要四个pld 而不仅仅是两个,否则你将永远有缓存未命中。您还可以同时使用vstvld 和/或尝试重新定位pld 指令。
猜你喜欢
  • 2015-04-23
  • 2011-05-11
  • 2011-05-05
  • 1970-01-01
  • 2011-12-04
  • 1970-01-01
  • 1970-01-01
  • 2013-01-12
  • 2011-06-30
相关资源
最近更新 更多