NOP
src/bin/spin-wait.rs の wait() ループ内にある nop() 呼び出しが何をしているのか、不思議に思うかもしれません。
答えは、文字どおり何もしていない、ということです。nop() 関数は、その箇所に
NOP という Arm の機械命令をプログラム内に配置するようコンパイラに指示します。NOP は特別な命令で、
CPU はそれを飛ばします。無視します。文字どおり、それに対して何の操作も行いません(名前の由来もそこにあります)。
では、その行を削除してプログラムを再コンパイルしましょう。--release モードを忘れないでください。その後、実行します。
また、少し暗めの単色 LED に戻りました。ループ本体がなくなったことで、コンパイラの最適化器は
wait() 関数が何もしていないと判断しました。そこで、コンパイル時にそれを丸ごと取り除いてしまったのです。ありがとう、最適化器。
おかげで私の待機ループは無限に高速になりました。
nop() はどのようにしてその役目を果たしているのでしょうか? nop() の実装を見ると、
(あちこちかなり掘り下げた先で)次のように実装されていることがわかります。
#![allow(unused)]
fn main() {
asm!("nop", options(nomem, nostack, preserves_flags));
}
nop() 関数は「インライン化」されるため、それを「呼び出す」と、その場所に実際の Arm の NOP アセンブリ命令が
プログラムのコードへ挿入されます。細かい事情により、この NOP はコンパイラによって削除されたり
別の場所へ移動されたりしません。あなたが置いたその場所に、きちんと留まります。
必要な箇所にアセンブリコードをプログラムへ挿入できる能力は、組み込みプログラミングではときに非常に重要です。
CPU には、コンパイラが知らない命令が存在することがありますが、それでも CPU を効果的に使うためには
それらが必要になることがあります。Rust の asm!() ディレクティブは、それを行う手段を提供します。
私たちのスピンウェイトは、まだひどいままです。もっと良くする方法について話しましょう。