我正在编写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 << 0)
和(1 << 16)
将在编译时计算,但(1 << i)
和(1 << (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。转换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编译器展开那个特定的循环,但似乎根据我的搜索它是不可能的......
答案 0 :(得分:5)
如果我们可以假设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和更旧的内核之外,它应该这样做。
另外:在嵌入式设备上编写中断处理程序时,函数的大小也会对性能产生影响,因为必须将指令加载到代码缓存中。精益代码通常执行得更快。如果将内存访问保存到不太可能在缓存中的内存,则可以节省一些开销。
答案 1 :(得分:1)
我可能会自己选择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);
事实上,你可以用宏来减少 less 的混乱(未经测试,但你应该得到一般的想法):
// 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可能比你更好的可能性: - )