最速のポーリングループ-1 CPUサイクルをトリミングするにはどうすればよいですか?


8

ARM Cortex M3(STM32F101と同様)のリアルタイムアプリケーションでは、内部ペリフェラルのレジスタのビットをゼロになるまでポーリングし、ループをできるだけタイトにします。ビットバンディングを使用して適切なビットにアクセスします。(動作する)Cコードは

while (*(volatile uint32_t*)kMyBit != 0);

そのコードは、オンチップ実行可能RAMにコピーされます。手動で最適化した後²、ポーリングループは次のようになり、6サイクルに設定しました。

0x00600200 681A      LDR      r2,[r3,#0x00]
0x00600202 2A00      CMP      r2,#0x00
0x00600204 D1FC      BNE      0x00600200

ポーリングの不確実性をどのようにして下げることができますか?5サイクルのループは私の目標に適合します。ゼロになった後、同じビットを15.5サイクルにできるだけ近づけてサンプリングします。

私の仕様では、少なくとも6.5 CPUクロックサイクルの低パルスを確実に検出することを求めています。持続時間が12.5サイクル未満の場合、確実に短いと分類します。そして、それが18.5サイクル以上続く限り、確実に分類します。パルスには、CPUクロックとの位相関係が定義されていません。これは、私の唯一の正確なタイミング基準です。これには、最大で5クロックのポーリングループが必要です。実際、私は5クロックサイクルでポーリングできる数十年前の8ビットCPUで実行されるコードをエミュレートしており、それが仕様になっています。


ループの前にNOPを挿入することでコードアライメントをオフセットしようとしましたが、多くのバリエーションで試しましたが、変化は見られませんでした。

CMPとLDRを反転させようとしましたが、それでも6サイクルが得られます。

0x00600200 681A      LDR      r2,[r3,#0x00]
; we loop here
0x00600202 2A00      CMP      r2,#0x00
0x00600204 681A      LDR      r2,[r3,#0x00]
0x00600206 D1FC      BNE      0x00600202

これは8サイクルです

0x00600200 681A      LDR      r2,[r3,#0x00]
0x00600202 681A      LDR      r2,[r3,#0x00]
0x00600204 2A00      CMP      r2,#0x00
0x00600206 D1FB      BNE      0x00600200

しかし、これは9サイクルです。

0x00600200 681A      LDR      r2,[r3,#0x00]
0x00600202 2A00      CMP      r2,#0x00
0x00600204 681A      LDR      r2,[r3,#0x00]
0x00600206 D1FB      BNE      0x00600200

interrupt割り込みが発生しない状況で、ビットが低い時間を測定します。

²最初のコンパイラ生成コードは、宛先レジスタとしてr12を使用し、ループに4コードバイトを追加したため、1サイクルかかりました。

given与えられた数値は、レジスターのアドレスでの読み取り時に、サイクルが正確であると思われるリアルタイムSTIceエミュレーターとそのエミュレータートリガー機能で取得されます。以前はループのブレークポイントで「状態」カウンターを試しましたが、結果はブレークポイントの場所によって異なります。シングルステップはさらに悪いことです。LDRには常に4サイクルが与えられますが、それが少なくとも3になる場合があります。


アラインメントが重要な場合、gpioクロックドメインはフラッシュ待機状態だけでなくパフォーマンスも支配する可能性があります。3+クロックになりますが、6 +以上になる場合もあります。ビットバンディングは読み取りのパフォーマンスヒットにはならないと思いますが、ゼロと比較して確認するのではなく、ビットをテストすることができます。一番下の行はそれを試してみなければなりません...
old_timer

1
Thumbモードには、別のレジスタをゼロに比較して分岐するためのcbnz命令がありませんか?でコンパイルしましたgcc -Os -mcpu=cortex-m3か?
Peter Cordes

1
@Peter Cordes:私はgccではなく、ArmCC 5(LLVMに移行する前のARMの以前の世代のコンパイラ)を使用しています。最適化は時間と最大値のためであり、CPUのオプションはIDEによって自動的に設定されることになっていますが、それを確認します。はい、CBZ / CBNZがありますが、私がドキュメントを読んだとき、後方に分岐することはできません。
fgrieu

1
さて、あなた(またはコンパイラ)は、ldr/ cbz reg, end_of_loopを内側に展開し、cmp/ bnzを一番下に展開することができます。しかし、それはあなたに不均一なポーリング間隔を与えるでしょう。
Peter Cordes、

2
仕様を誤って解釈していないか?「CPUサイクルではないデバイス固有のサイクル」(たとえば、タイマーまたは独自のクロックソースを備えたUARTがはるかに遅いサイクル)に言及している場合の仕様かもしれません。 13000 CPU固有のサイクルとして」
ブレンダン

回答:


8

私が質問を正しく理解していれば、削減する必要があるのは必ずしもループサイクルではなく、結果として生じるサンプル(つまり、LDR命令)間のサイクル数です。ただし、反復ごとに複数のLDRが存在する可能性があります。あなたはこのようなことを試すことができます:

    ldrb    r1, [r0]

loop:
    cbz     r1, out
    ldrb    r2, [r0]
    cbz     r2, out
    ldrb    r1, [r0]
    b       loop

out:

2つのLDRB命令間の間隔は変化するため、サンプルの間隔は均一ではありません。

これはループの終了をわずかに遅らせるかもしれませんが、問題の説明からそれが重要かどうかは言えません。

私はたまたまサイクル精度の高いM7モデルにアクセスできます。プロセスが元のループを安定させると、M7で反復ごとに3サイクル(LDRは3サイクルごと)で実行されますが、上記の提案されたループは4サイクルで実行されますが、そこに2つのLDR(2サイクルごとにLDR)。サンプリングレートは間違いなく改善されています。

信用を与えるために、休憩としてのCBZでの展開は、コメントで@Peter Cordesによって提案されました

確かにM3は遅くなりますが、それがあなたが求めているサンプリングレートである場合は、一撃の価値があります。

また、(上記のコードのように)LDRではなくLDRBが何かを変更するかどうかを確認することもできますが、私はそれを期待していません。

UPD:別の2-LDRループバージョンがあり、M7では3サイクルで完了します。これは、興味のあることを試すことができます(また、CBZブレークにより、ループ後のパスのバランスを簡単に調整できます)。

    ldr     r1, [r0]

loop:
    ldr     r2, [r0]
    cbz     r1, out_slow
    cbz     r2, out_fast
    ldr     r1, [r0]
    b       loop

out_fast:
    /* NOPs as required */

out_slow:

朗報:2サンプリングで10サイクル/ループで実行されるため、平均サンプルレートは問題ありません。深刻な問題:サンプル間の遅延がに行く1から4サイクルであるr2に行くものにr1、しかし6サイクルに行く1からr1に行く1にr2(のためにb loop、私は(最大で)したいときには、間にある)5サンプリング間のサイクル。簡単に修正できる問題はout、exit r1がゼロの場合よりもゼロの場合の方が、出口がゼロの場合よりも遅れて到達することですr2。他のニュースでldrbは、ビットバンディングアドレスが問題を引き起こしたため、に変更されましたldr
fgrieu

2
基本的にうまくいったことを確認する前に、サンプルのイコライゼーションを行いたくありませんでした。また、私のM7がどのようにM3に変換されるかを予測することは困難です。M7で均一なサンプルレート(2サイクルごとにLDR)を生成したので、ループバージョンを提供しました。しかし、私は実際にM7でより高速になった別のバージョンも持っていました(ループあたり3サイクル、それでも2つのLDR)、興味のあるものを試してみることができます。回答を更新します。
alex_mv

3
私のCortex-M3 emuで2つの等間隔のサンプルを使用して、2番目のポーリングループが10サイクルで実行されることを確認しました。それは私の答え(現在は削除されています)を単純化して切り捨て、より短いパルスをテストできるようにします。2つのnoploop:と4つのnopout_fast:は、ldrアフターout_slow:サンプルが最初にゼロで見られたサンプルの10サイクル後の3つのうちいずれかであるようにします。私の仕様(質問で述べたとおり)には13が必要ですが、調整するのは簡単です。問題は100%解決しました!ピーター・コーデスのコメント、および最初の賞金を獲得したB Deganに感謝します。
fgrieu

@fgrieu:そうそう、それは最新のアップデートで巧妙なトリックです。 分岐予測(存在する場合)を汚染せずにCPUでNOPよりもサイクルがかかる場合は、out_fast5 nop秒よりもコンパクトで、おそらく別のをldr実行するbか、次の命令を実行する可能性があります。
Peter Cordes

1

あなたはこれを試すことができますが、同じ6サイクルを与えるのではないかと疑っています

0x00600200 581a      LDR      r2,[r3,r0]; initialize r0 to 0x0
0x00600202 4282      CMP      r2,r0
0x00600204 D1FC      BNE      0x00600200

4
私が試した:6サイクル:-(
fgrieu
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.