【问题标题】:Loop unroll (with bitwise operations)循环展开(按位运算)
【发布时间】:2012-09-13 07:23:21
【问题描述】:

我正在编写一个 Linux 内核驱动程序(for ARM)并且在一个 irq 处理程序中我需要检查中断位。

bit
 0/16  End point 0 In/Out interrupt
       (very likely, while In is more likely)
 1/17  End point 1 In/Out interrupt
 ...
15/31  End point 15 In/Out interrupt

请注意,一次可以设置多个位。

所以这是代码:

int i;
u32 intr = read_interrupt_register();

/* ep0 IN */
if(likely(intr & (1 << 0))){
    handle_ep0_in();
}

/* ep0 OUT */
if(likely(intr & (1 << 16))){
    handle_ep0_out();
}

for(i=1;i<16;++i){
    if(unlikely(intr & (1 << i))){
        handle_ep_in(i);
    }
    if(unlikely(intr & (1 << (i + 16)))){
        handle_ep_out(i);
    }
}

(1 &lt;&lt; 0)(1 &lt;&lt; 16) 将在编译时计算,但 (1 &lt;&lt; i)(1 &lt;&lt; (i + 16)) 不会。循环中也会有积分比较和加法。

因为它是一个 irq 处理程序,所以应该在最短的时间内完成工作。这让我想想我是否需要优化一下。

可能的方法?

1.拆分循环,好像没什么区别...

/* ep0 IN */
if(likely(intr & (1 << 0))){
    handle_ep0_in();
}

/* ep0 OUT */
if(likely(intr & (1 << 16))){
    handle_ep0_out();
}

for(i=1;i<16;++i){
    if(unlikely(intr & (1 << i))){
        handle_ep_in(i);
    }
}
for(i=17;i<32;++i){
    if(unlikely(intr & (1 << i))){
        handle_ep_out(i - 16);
    }
}

2. Shift intr 而不是要比较的值?

/* ep0 IN */
if(likely(intr & (1 << 0))){
    handle_ep0_in();
}

/* ep0 OUT */
if(likely(intr & (1 << 16))){
    handle_ep0_out();
}

for(i=1;i<16;++i){
    intr >>= 1;
    if(unlikely(intr & 1)){
        handle_ep_in(i);
    }
}
intr >>= 1;
for(i=1;i<16;++i){
    intr >>= 1;
    if(unlikely(intr & 1)){
        handle_ep_out(i);
    }
}

3. 完全展开循环(未显示)。那会让代码有点乱。

4.还有其他更好的方法吗?

5. 还是说编译器实际上会生成最优化的方式?


编辑:我正在寻找一种方法来告诉 gcc 编译器展开该特定循环,但根据我的搜索似乎不可能......

【问题讨论】:

  • 您只有 17 个元素需要处理。手动展开它并不比第一个示例中的代码更混乱

标签: c linux-kernel bit-manipulation loop-unrolling


【解决方案1】:

如果我们可以假设 intr 中设置的位数很少(通常在中断掩码中就是这种情况),我们可以稍微优化一下并编写一个循环,每个位只执行一次:

void handle (int intr)
{
  while (intr)
  {
    // find index of lowest bit set in intr:
    int bit_id = __builtin_ffs(intr)-1;

    // call handler:
    if (bit_id > 16)
      handle_ep_out (bit_id-16);
    else
      handle_ep_in (bit_id);

    // clear that bit
    // (I think there was a bit-hack out there to simplify this step even further)
    intr -= (1<<bit_id);
  }
}

在大多数 ARM 架构上,__builtin_ffs 将编译为 CLZ 指令和一些围绕它的算术。除了 ARM7 和更早的内核之外,它应该对任何东西都这样做。

另外:在嵌入式设备上编写中断处理程序时,函数的大小也会影响性能,因为指令必须加载到代码缓存中。精益代码通常执行得更快。如果您将内存访问保存到不太可能在缓存中的内存,则有点开销是可以的。

【讨论】:

  • 您错过了无参数函数 handle_ep0_inhandle_ep0_out 的特殊情况,但为 ffs +1
  • 另外我不知道内核中是否允许 __builtin_ffs,但如果不允许,他们很可能会有一些替代品。
  • 为什么不允许。如果不是,您可以直接通过内联 asm 使用 clz。
  • @nils-pipenbrinck:是的,ffs,如果在 __ffs 中可用,则带有特定于架构的版本。
  • @dbrank0:通常这种限制的原因是编译器支持库,它没有在内核中使用。在此处搜索“udivdi3”。
【解决方案2】:

我自己可能会选择选项 5。代码的可读性,让 gcc 的疯狂优化级别 -O3 尽其所能。

我见过在那个级别生成的代码,我什至无法理解。

C 中的任何手工优化(除了可能展开和使用常量而不是运行时位移,一个 la 选项 3)都不太可能超过编译器本身的性能。

我想你会发现展开可能没有你想的那么乱:

if (  likely(intr & 0x00000001)) handle_ep0_in();
if (  likely(intr & 0x00010000)) handle_ep0_out();

if (unlikely(intr & 0x00000002)) handle_ep_in(1);
if (unlikely(intr & 0x00020000)) handle_ep_out(1);

:

if (unlikely(intr & 0x00008000)) handle_ep_in(15);
if (unlikely(intr & 0x80000000)) handle_ep_out(15);

事实上,你可以用宏来让事情变得更混乱(未经测试,但你应该明白一般的想法):

// Since mask is a constant, "mask << 32" should be too.

# define chkintr (mask, num) \
    if (unlikely(intr & (mask      ))) handle_ep_in  (num); \
    if (unlikely(intr & (mask << 32))) handle_ep_out (num);

// Special case for high probability bit.

if (likely(intr & 0x00000001UL)) handle_ep0_in();
if (likely(intr & 0x00010000UL)) handle_ep0_out();

chkintr (0x0002UL,  1);  chkintr (0x0004UL,  2);  chkintr (0x0008UL,  3);
chkintr (0x0010UL,  4);  chkintr (0x0020UL,  5);  chkintr (0x0040UL,  6);
chkintr (0x0080UL,  7);  chkintr (0x0100UL,  8);  chkintr (0x0200UL,  9);
chkintr (0x0400UL, 10);  chkintr (0x0800UL, 11);  chkintr (0x1000UL, 12);
chkintr (0x2000UL, 13);  chkintr (0x4000UL, 14);  chkintr (0x8000UL, 15);

唯一的进步是手工编码汇编语言,仍然 gcc 很有可能胜过你:-)

【讨论】:

  • 好吧,我可能会过度担心,因为它不会真正使用太多时间,但我仍然不想对自己撒谎:P。另外我认为 Linux Kernel 默认使用优化级别 2 构建。
  • 2 级可能就足够了。当然,内核中的某些东西已经具有非常严格的时序要求,所以也许-O2 就足够了。 -O3 可能会使内核调试成为一场噩梦。底线建议,在您知道这是一个问题之前不要担心这个。循环和展开形式都可能足够快。
  • 好吧,让我们看看是否还有更多答案。
猜你喜欢
  • 2011-06-20
  • 1970-01-01
  • 1970-01-01
  • 2016-08-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-07-23
  • 1970-01-01
相关资源
最近更新 更多