6

Linuxカーネルドライバー(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

一度に1ビット以上を設定できることに注意してください。

だからこれはコードです:

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コンパイラにその特定のループを展開するように指示する方法を探していましたが、私の検索によればそれは不可能のようです...

4

2 に答える 2

5

intrのセットビット数が少ないと想定できる場合(通常は割り込みマスクの場合のように)、少し最適化して、各ビットに対して1回だけ実行されるループを作成できます。

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以前のコア以外の場合はそうする必要があります。

また、組み込みデバイスで割り込みハンドラーを作成する場合、命令をコードキャッシュにロードする必要があるため、関数のサイズによってパフォーマンスも異なります。リーンコードは通常、より高速に実行されます。キャッシュにある可能性が低いメモリへのメモリアクセスを保存する場合は、少しオーバーヘッドがあります。

于 2012-09-13T07:43:30.720 に答える
1

私はおそらく自分でオプション5を選ぶでしょう。読みやすさをコード化し、gccの非常識な最適化レベル-O3にできることを実行させます。

そのレベルで生成された、理解すらできないコードを見たことがあります。

Cでの手作りの最適化(実行時のビットシフトではなく定数を展開して使用することを除いて、オプション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があなたをしのぐことができる可能性はまだ十分にあります:-)

于 2012-09-13T07:30:48.933 に答える