マルチバイト NOP 命令のことです。
はじめに
プログラムをしているとき,とくに意味を持たず適当な名前を付けたい場合がある。そういう時にデフォルトで用いられる名前を メタ構文変数 - wikipedia と呼ぶらしい。日本では hoge
が有名だが,英語圏では一般的に下記の順に変数名を用いるようだ。
foo
bar
baz
qux
quux
ただし,必ずしも某アニメのように6番目以降 quuux
,quuuux
,quuuuux
, quuuuuux
と続くわけでは無いようだ。
さて,突然こんな話題を上げたのは以下の理由による。
謎の機械語コード
さて本記事のタイトルを回収しよう。先日,自作のプログラムにおける intel C++ コンパイラの最適化ぶりをチェックするため,生成した実行ファイルの逆アセンブリを眺めていたところ,
$\color{red}{\Huge \textsf{なんじゃこりゃぁぁぁ}}$
と思ったのが下記の機械語コードである。
※該当行のみ抽出,説明の都合上アドレスを入れ替えた。
命令コードの 66h
は,インテルの 80386 プロセッサ以降に追加されたオペランドサイズ・オーバーライド・プリフィクスである。デフォルトのオペランドサイズ(32bit or 16bit)に対して,プリフィクスを追加した命令に限りオペランドサイズ(32bit or 16bit)を反転させる。ただし,このプリフィクスを二つ連続して付与したからといって元に戻る訳でもないし,プリフィクスを重ねればオペランドサイズが 64bit,128bit,256bit と増えていく訳でもない。要するに一つだけで良いのだ。
種明かし
実をいうと,これらはマルチバイト NOP 命令と呼ばれる正規のバイト列だ。
賢明な読者はすでに気づいたかもしれないが,アドレスの下位4ビットと命令長の和が常に16になっている。実は,これらのマルチバイト NOP 命令の次のアドレスはジャンプ先になっており,本マルチバイト NOP 命令を介さず分岐命令によって飛んでくる場合の合流ポイントになっているのだ。このとき次のアドレスが16バイトの倍数になるようアラインメント調整を行うことで命令フェッチの効率を上げているのだと思われる。
以下は自作プログラムを intel C++ コンパイラでビルドして作られた実行ファイルを逆アセンブルして抽出したものだ。なお,長さ1~9バイトまでは intel のソフトウェア・デベロッパーズ・マニュアルに記載されている内容と一致している。x86 アーキテクチャにおける命令長は最大15バイト以下と決められているので,16バイト以上のマルチバイト NOP 命令は存在しない。また,これらのマルチバイト NOP 命令の大半はメモリオペランドを持つが,これらのアドレスが無効であっても例外は発生しない。
長さ | バイト列 | アセンブリ |
---|---|---|
1 | 90 |
nop / xchg eax, eax
|
2 | 66 90 |
nop / xchg ax, ax
|
3 | 0F 1F 00 |
nop dword ptr [eax] |
4 | 0F 1F 40 00 |
nop dword ptr [eax+00h] |
5 | 0F 1F 44 00 00 |
nop dword ptr [eax+eax*1+00h] |
6 | 66 0F 1F 44 00 00 |
nop word ptr [eax+eax*1+00h] |
7 | 0F 1F 80 00 00 00 00 |
nop dword ptr [eax+00000000h] |
8 | 0F 1F 84 00 00 00 00 00 |
nop dword ptr [eax+eax*1+00000000h] |
9 |
66 0F 1F 84 00 00 00 00 00
|
nop word ptr [eax+eax*1+00000000h] |
10 |
66 2E 0F 1F 84 00 00 00 00 00
|
nop word ptr cs:[eax+eax*1+00000000h] |
11 |
66 66 2E 0F 1F 84 00 00 00 00 00
|
nop word ptr cs:[eax+eax*1+00000000h] |
12 |
66 66 66 2E 0F 1F 84 00 00 00 00 00
|
nop word ptr cs:[eax+eax*1+00000000h] |
13 |
66 66 66 66 2E 0F 1F 84 00 00 00 00 00
|
nop word ptr cs:[eax+eax*1+00000000h] |
14 |
66 66 66 66 66 2E 0F 1F 84 00 00 00 00 00
|
nop word ptr cs:[eax+eax*1+00000000h] |
15 |
66 66 66 66 66 66 2E 0F 1F 84 00 00 00 00 00
|
nop word ptr cs:[eax+eax*1+00000000h] |
Microsoft Visual C++ の場合
一方,Visual C++ でアセンブリ出力させると,アラインメント調整要素 npad
の存在に気づく。npad
はジャンプ先(ラベル)の直前に必ず挿入される訳ではなく,ループのジャンプ先のように繰り返し飛んでくる(可能性が高い)場所に挿入されるようだ。
この npad
はマクロであり,Visual Studio 2022 Community Edition では下記のファイルで定義されている。
C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.41.34120\include\listing.inc
ちなみに npad
マクロの内容は下記のようになっている。9バイトまでは intel のソフトウェア・デベロッパーズ・マニュアルの内容と一致しており,intel C++ コンパイラと同じである。しかし10バイト以上は intel C++ コンパイラと異なり,とくに12バイト以上は2つの命令に分かれている。ただし,筆者が確認した範囲では Visual C++ が9バイト以上のマルチバイト NOP 命令を出力した例を確認できなかった。
参考文献 [5] では11バイトのマルチバイト NOP 命令を確認しているので筆者の調査不足と思われる。
長さ | バイト列 | アセンブリ |
---|---|---|
1 | 90 |
nop / xchg eax, eax
|
2 | 66 90 |
nop / xchg ax, ax
|
3 | 0F 1F 00 |
nop dword ptr [eax] |
4 | 0F 1F 40 00 |
nop dword ptr [eax+00h] |
5 | 0F 1F 44 00 00 |
nop dword ptr [eax+eax*1+00h] |
6 | 66 0F 1F 44 00 00 |
nop word ptr [eax+eax*1+00h] |
7 | 0F 1F 80 00 00 00 00 |
nop dword ptr [eax+00000000h] |
8 | 0F 1F 84 00 00 00 00 00 |
nop dword ptr [eax+eax*1+00000000h] |
9 | 66 0F 1F 84 00 00 00 00 00 |
nop word ptr [eax+eax*1+00000000h] |
10 | 66 66 0F 1F 84 00 00 00 00 00 |
nop word ptr [eax+eax*1+00000000h] |
11 | 66 66 66 0F 1F 84 00 00 00 00 00 |
nop word ptr [eax+eax*1+00000000h] |
12 |
0F 1F 40 00 0F 1F 84 00 00 00 00 00
|
nop dword ptr [eax+00h] nop dword ptr [eax+eax*1+00000000h]
|
13 |
0F 1F 40 00 66 0F 1F 84 00 00 00 00 00
|
nop dword ptr [eax+00h] nop word ptr [eax+eax*1+00000000h]
|
14 |
0F 1F 40 00 66 66 0F 1F 84 00 00 00 00 00
|
nop dword ptr [eax+00h] nop word ptr [eax+eax*1+00000000h]
|
15 |
0F 1F 40 00 66 66 66 0F 1F 84 00 00 00 00 00
|
nop dword ptr [eax+00h] nop word ptr [eax+eax*1+00000000h]
|
マルチバイト NOP 命令の注意点
(1) レジスタストール問題
参考文献 [6] には以下の注意事項が記されている。
1 バイト NOP (XCHG EAX,EAX) はハードウェアによって特別にサポートされます。この NOP は 1 つのマイクロオペレーション (uop) とそれに関連するリソースを使用しますが、EAX の元の値への依存関係は解消されます。このマイクロオペレーション (uop) は、パイプラインの早い段階で実行できるため、未処理の命令の数を減らすことができ、最もコストの小さい NOP と言えます。
その他の NOP には、ハードウェアの特別なサポートはありません。これらの NOP の入力レジスターと出力レジスターは、ハードウェアによって解釈されます。したがって、コード・ジェネレーターは、NOP ができるだけ早い時期に RS リソースをディスパッチして解放できるように、最も古い値を保持しているレジスターを入力としてコードを構成する必要があります。
すなわち,表1・2に示すマルチバイト NOP 命令は EAX レジスタを使用しているので,直前に EAX レジスタを使用している命令が存在するとレジスタストールが発生することになる。マルチバイト NOP 命令としては別に EAX レジスタ以外を使用しても良いはずだが,筆者の調査した範囲では EAX レジスタ以外の使用例は見つからなかった。
かつて intel MPX (Memory Protection Extensions) という機能拡張があった。第6~8世代 Core プロセッサの頃に追加されたが,第10世代には引き継がれず廃止されたという黒歴史である。参考文献 [9] によると,これらの命令はマルチバイト NOP 命令の EAX レジスタ以外のエンコード領域に割り当てられたようで,廃止されたとはいえ EAX レジスタ以外は使いづらい状況である。
(2) プリフィクス使い過ぎ問題
また,参考文献 [7] の「旧世代の Intel Atom マイクロアーキテクチャーとソフトウェアの最適化」を見ると次のような記述がある。
複数のプリフィクスを持つ命令は、デコーダーのスループットを制限します。プリフィクスとエスケープの合計バイト数が制限に当てはまります。命令のプリフィクス + エスケープは、以下のマイクロアーキテクチャーの制限を超えないようにします。
- Silvermont マイクロアーキテクチャー: 3 バイトを超えるとペナルティーが発生します。
- Goldmont マイクロアーキテクチャー以降: 4 バイトを超えるとペナルティーが発生します。したがって、上位 8 つのレジスターをアクセスするインテル SSE4 や AES 命令には、ペナルティーが科せられません。
- Silvermont と Goldmont マイクロアーキテクチャーでは、デコーダー 0 のみがプリフィクス/エスケープ・バイトの制限を超えた命令をデコードできます。
ちなみに 66h
はオペランドサイズ・オーバーライド・プリフィクス,2Eh
はセグメント・オーバーライド・プリフィクス,0Fh
はエスケープコードであるから,表1の長さ15バイトのマルチバイト NOP 命令はプリフィクスとエスケープコードだけで8バイトを消費していることになり,旧世代のプロセッサではペナルティが大きいと思われる。
マルチバイト NOP 命令の効果
プリフィクスを6個も7個も付けてしまうとパフォーマンスの低下が気になるので検証することにした。
1~15バイトまでのマルチバイト NOP 命令を 1800 bytes 分並べ,それを 400,000,000 回繰り返して実行時間を計測する。コア部分はアセンブラで記述し,C言語から呼び出すようにした。
計測環境を以下に示す。
- intel Core i-7 12700 プロセッサ 2.1GHz(最大 4.9GHz), 32GB
- Windows10 22H2
- Visual Studio 2022 Community Edition
32bit 版 C++ コンパイラ及びアセンブラを使用
なぜ 1800 bytes かというと,Golden Cove の uOPs キャッシュ(4Kエントリ)に収まるようにしたからである。
計測結果を以下に示す。総実行時間 $t$ とおくと,1800 bytes 分の実行時間 $t_1 = t / 400000000$ であり,最大動作周波数 $\phi$ とおくと,1サイクルあたりの並列実行数 $p$ は次の式で表される。
p = \frac{\displaystyle\frac{1800}{n}}{t_1 \times \phi}
長さ | intel C++ Compiler |
Microsoft Visual C++ |
---|---|---|
1 | 65.248 | 同左 |
2 | 31.660 | 同左 |
3 | 23.290 | 同左 |
4 | 15.558 | 同左 |
5 | 12.515 | 同左 |
6 | 10.365 | 同左 |
7 | 8.863 | 同左 |
8 | 7.773 | 同左 |
9 | 6.920 | 同左 |
10 | 6.273 | 6.240 |
11 | 5.693 | 5.690 |
12 | 5.223 | 10.350 |
13 | 4.828 | 9.535 |
14 | 4.540 | 8.918 |
15 | 4.345 | 8.283 |
長さ | intel C++ Compiler |
Microsoft Visual C++ |
---|---|---|
1 | 5.630 | 同左 |
2 | 5.801 | 同左 |
3 | 5.258 | 同左 |
4 | 5.903 | 同左 |
5 | 5.871 | 同左 |
6 | 5.907 | 同左 |
7 | 5.921 | 同左 |
8 | 5.908 | 同左 |
9 | 5.898 | 同左 |
10 | 5.856 | 5.887 |
11 | 5.867 | 5.869 |
12 | 5.862 | 2.958 |
13 | 5.853 | 2.964 |
14 | 5.780 | 2.942 |
15 | 5.636 | 2.957 |
こういうのはグラフにすると一目瞭然である。intel C++ コンパイラの吐くマルチバイト NOP 命令のほうは綺麗な反比例になっている。すなわち命令長は長くとも1バイトの NOP 命令と同じ時間で実行できているのだ。一方,Microsoft Visual C++ コンパイラの出力を見てみると,11バイト以下は1命令なのでintel C++ コンパイラと同じだが,12バイト以上では2命令に分割してしまうのでちょうど2倍の時間を要している。
◆intel C++ Compiler, ◆Microsoft Visual C++
1サイクルあたりの並列実行数を求めてみると,ほぼ6命令を並列実行できているように思える。
参考文献 [8] によると Golden Cove のアウト・オブ・オーダーエンジンは最大6命令を発行可能である。実行ユニットである ALU や LEA は5個しかないが,たぶんそれらを使わないので済むので6命令を並列実行できていると思われる。
旧世代 Core プロセッサの場合
下記の環境でも調べてみた。
- intel Core i5-10400 プロセッサ 2.9GHz(最大 4.3GHz), 16GB
- Windows 10 22H2
Core i7-12700 プロセッサの結果と比較してみよう。
コンパイラ | 長 さ |
i7- 12700 |
i5- 10400 |
---|---|---|---|
intel C++ Compiler |
1 | 65.248 | 107.230 |
2 | 31.660 | 53.940 | |
3 | 23.290 | 36.003 | |
4 | 15.558 | 27.003 | |
5 | 12.515 | 21.598 | |
6 | 10.365 | 17.740 | |
7 | 8.863 | 15.433 | |
8 | 7.773 | 13.533 | |
9 | 6.920 | 13.423 | |
10 | 6.273 | 13.413 | |
11 | 5.693 | 12.190 | |
12 | 5.223 | 9.078 | |
13 | 4.828 | 8.313 | |
14 | 4.540 | 7.788 | |
15 | 4.345 | 7.228 | |
Microsoft Visual+ C++ |
10 | 6.240 | 13.410 |
11 | 5.690 | 12.190 | |
12 | 10.350 | 17.913 | |
13 | 9.535 | 16.560 | |
14 | 8.918 | 15.505 | |
15 | 8.283 | 14.443 |
コンパイラ | 長 さ |
i7- 12700 |
i5- 10400 |
---|---|---|---|
intel C++ Compiler |
1 | 5.630 | 3.904 |
2 | 5.801 | 3.880 | |
3 | 5.258 | 3.876 | |
4 | 5.903 | 3.876 | |
5 | 5.871 | 3.876 | |
6 | 5.907 | 3.933 | |
7 | 5.921 | 3.875 | |
8 | 5.908 | 3.867 | |
9 | 5.898 | 3.465 | |
10 | 5.856 | 3.121 | |
11 | 5.867 | 3.122 | |
12 | 5.862 | 3.843 | |
13 | 5.853 | 3.874 | |
14 | 5.780 | 3.840 | |
15 | 5.636 | 3.861 | |
Microsoft Visual+ C++ |
10 | 5.887 | 3.122 |
11 | 5.869 | 3.122 | |
12 | 2.958 | 1.947 | |
13 | 2.964 | 1.944 | |
14 | 2.942 | 1.928 | |
15 | 2.957 | 1.932 |
こういうのはグラフにすると分かり易い。第10世代Coreプロセッサの場合,最大命令発行数は4個っぽい。ただし長さ10~11バイトにかけて並列度が約3個に落ちてしまうのが謎である。長すぎるプリフィクスに対するペナルティであるならば,12バイト以上で並列度が4個に戻らないからだ。
◆ Core i7-12700 intel C++ Compiler,◆ Core i7-12700 Microsoft Visual C++
◆ Core i5-10400 intel C++ Compiler,◆ Core i5-10400 Microsoft Visual C++
結論
インテルは流石である。伊達にパフォーマンスが下がるようなコードは吐かない。いくらプリフィクスが付いていても uOPs キャッシュに入ってしまえば関係ないのだろう。
加えて,レジスタストール問題についても最近の Core プロセッサであれば(おそらく)起こらないのではないかと考える。というのも参考文献 [10] によれば,Sandy Bridge 以降の Core プロセッサには Zeroing Idioms という新機能が追加されたからだ。これは xor eax, eax
とか sub eax, eax
のような命令に適用される。これらの命令はレジスタの元の値に関係なく結果がゼロになるので依存関係は切れ,並列に実行できるようになる。
であれば,最近の Core プロセッサであれば,マルチバイト NOP 命令についても同様にストールを起こさないような工夫を施すことは簡単であろうし,実際に実現しているのではないかと思われる。
もっと旧世代の Core プロセッサの場合
下記の環境でも調べてみた。
- intel Core i7-3770 プロセッサ 3.4GHz(最大 3.9GHz), 16GB
- Windows 10 22H2
命令長 $n$,総実行時間 $t$,最大動作周波数 $\phi$ とおくと,並列実行数 $p$ は次の式で表される。
p = \frac{400000000 \times \displaystyle\frac{1800}{n}}{t \times \phi}
コンパイラ | 長 さ |
総実行時間(s) | 並列実行数(個) | ||||
---|---|---|---|---|---|---|---|
i7- 12700 |
i5- 10400 |
i7- 3770 |
i7- 12700 |
i5- 10400 |
i7- 3770 |
||
intel C++ Compiler |
1 | 26.099 | 42.892 | 46.788 | 5.630 | 3.904 | 3.946 |
2 | 12.664 | 21.576 | 23.424 | 5.801 | 3.880 | 3.941 | |
3 | 9.316 | 14.401 | 15.611 | 5.258 | 3.876 | 3.942 | |
4 | 6.223 | 10.801 | 11.710 | 5.903 | 3.876 | 3.941 | |
5 | 5.006 | 8.639 | 9.432 | 5.871 | 3.876 | 3.915 | |
6 | 4.146 | 7.096 | 7.805 | 5.907 | 3.933 | 3.942 | |
7 | 3.545 | 6.173 | 6.691 | 5.921 | 3.875 | 3.942 | |
8 | 3.109 | 5.413 | 6.006 | 5.908 | 3.867 | 3.842 | |
9 | 2.768 | 5.369 | 6.013 | 5.898 | 3.465 | 3.411 | |
10 | 2.509 | 5.365 | 6.009 | 5.856 | 3.121 | 3.072 | |
11 | 2.277 | 4.876 | 6.119 | 5.867 | 3.122 | 2.743 | |
12 | 2.089 | 3.631 | 6.016 | 5.862 | 3.843 | 2.557 | |
13 | 1.931 | 3.325 | 6.006 | 5.853 | 3.874 | 2.364 | |
14 | 1.816 | 3.115 | 6.012 | 5.780 | 3.840 | 2.193 | |
15 | 1.738 | 2.891 | 6.016 | 5.636 | 3.861 | 2.046 | |
Microsoft Visual C++ |
10 | 2.496 | 5.364 | 6.016 | 5.887 | 3.122 | 3.069 |
11 | 2.276 | 4.876 | 6.015 | 5.869 | 3.122 | 2.790 | |
12 | 4.140 | 7.165 | 7.831 | 2.958 | 1.947 | 1.965 | |
13 | 3.814 | 6.624 | 7.255 | 2.964 | 1.944 | 1.957 | |
14 | 3.567 | 6.202 | 6.741 | 2.942 | 1.928 | 1.956 | |
15 | 3.313 | 5.777 | 6.276 | 2.957 | 1.932 | 1.961 |
グラフは重なって見にくいので第10世代を省いた。第3世代も10バイト以下の特性は第10世代と変わらないが,11バイト以上になると並列実行数の緩やかな低下が見られる。
12バイト以上になると Microsoft Visual C++ コンパイラは2命令に分割して出力するため,第10世代以降では intel C++ コンパイラのちょうど半分に低下するが,第3世代はその差が少なく15バイトになると変わらなくなる。第3世代まで古くなると懸念されたプリフィクス使い過ぎ問題が発生するのか,もしくは命令長自体が無くなり過ぎると何らかのペナルティが起こるのかもしれない。
◆ Core i7-12700 intel C++ Compiler,◆ Core i7-12700 Microsoft Visual C++
◆ Core i7-3770 intel C++ Compiler,◆ Core i7-3770 Microsoft Visual C++
おまけ
今回の検証に用いたソースコード一式を以下に示す。
x86 アセンブラ部のソースはコチラ
すっごい久しぶりにアセンブラのコードを書いたわ。
.MODEL FLAT
.CODE
;-------------------------------------------------------------------------------
PUBLIC _nop1
_nop1 PROC
mov ecx, 400000000
ALIGN 16
@@:
REPEAT 1800
DB 90h
ENDM
dec ecx
jnz @b
ret
_nop1 ENDP
;-------------------------------------------------------------------------------
PUBLIC _nop2
_nop2 PROC
mov ecx, 400000000
ALIGN 16
@@:
REPEAT 900
DB 66h, 90h
ENDM
dec ecx
jnz @b
ret
_nop2 ENDP
;-------------------------------------------------------------------------------
PUBLIC _nop3
_nop3 PROC
mov ecx, 400000000
ALIGN 16
@@:
REPEAT 600
DB 0Fh, 1Fh, 00h
ENDM
dec ecx
jnz @b
ret
_nop3 ENDP
;-------------------------------------------------------------------------------
PUBLIC _nop4
_nop4 PROC
mov ecx, 400000000
ALIGN 16
@@:
REPEAT 450
DB 0Fh, 1Fh, 40h, 00h
ENDM
dec ecx
jnz @b
ret
_nop4 ENDP
;-------------------------------------------------------------------------------
PUBLIC _nop5
_nop5 PROC
mov ecx, 400000000
ALIGN 16
@@:
REPEAT 360
DB 0Fh, 1Fh, 44h, 00h, 00h
ENDM
dec ecx
jnz @b
ret
_nop5 ENDP
;-------------------------------------------------------------------------------
PUBLIC _nop6
_nop6 PROC
mov ecx, 400000000
ALIGN 16
@@:
REPEAT 300
DB 66h, 0Fh, 1Fh, 44h, 00h, 00h
ENDM
dec ecx
jnz @b
ret
_nop6 ENDP
;-------------------------------------------------------------------------------
PUBLIC _nop7
_nop7 PROC
mov ecx, 400000000
ALIGN 16
@@:
REPEAT 257
DB 0Fh, 1Fh, 80h, 00h, 00h, 00h, 00h
ENDM
dec ecx
jnz @b
ret
_nop7 ENDP
;-------------------------------------------------------------------------------
PUBLIC _nop8
_nop8 PROC
mov ecx, 400000000
ALIGN 16
@@:
REPEAT 225
DB 0Fh, 1Fh, 84h, 00h, 00h, 00h, 00h, 00h
ENDM
dec ecx
jnz @b
ret
_nop8 ENDP
;-------------------------------------------------------------------------------
PUBLIC _nop9
_nop9 PROC
mov ecx, 400000000
ALIGN 16
@@:
REPEAT 200
DB 66h
DB 0Fh, 1Fh, 84h, 00h, 00h, 00h, 00h, 00h
ENDM
dec ecx
jnz @b
ret
_nop9 ENDP
;-------------------------------------------------------------------------------
PUBLIC _nop10
_nop10 PROC
mov ecx, 400000000
ALIGN 16
@@:
REPEAT 180
DB 66h, 2Eh
DB 0Fh, 1Fh, 84h, 00h, 00h, 00h, 00h, 00h
ENDM
dec ecx
jnz @b
ret
_nop10 ENDP
;-------------------------------------------------------------------------------
PUBLIC _nop11
_nop11 PROC
mov ecx, 400000000
ALIGN 16
@@:
REPEAT 164
DB 66h, 66h, 2Eh
DB 0Fh, 1Fh, 84h, 00h, 00h, 00h, 00h, 00h
ENDM
dec ecx
jnz @b
ret
_nop11 ENDP
;-------------------------------------------------------------------------------
PUBLIC _nop12
_nop12 PROC
mov ecx, 400000000
ALIGN 16
@@:
REPEAT 150
DB 66h, 66h, 66h, 2Eh
DB 0Fh, 1Fh, 84h, 00h, 00h, 00h, 00h, 00h
ENDM
dec ecx
jnz @b
ret
_nop12 ENDP
;-------------------------------------------------------------------------------
PUBLIC _nop13
_nop13 PROC
mov ecx, 400000000
ALIGN 16
@@:
REPEAT 138
DB 66h, 66h, 66h, 66h, 2Eh
DB 0Fh, 1Fh, 84h, 00h, 00h, 00h, 00h, 00h
ENDM
dec ecx
jnz @b
ret
_nop13 ENDP
;-------------------------------------------------------------------------------
PUBLIC _nop14
_nop14 PROC
mov ecx, 400000000
ALIGN 16
@@:
REPEAT 129
DB 66h, 66h, 66h, 66h, 66h, 2Eh
DB 0Fh, 1Fh, 84h, 00h, 00h, 00h, 00h, 00h
ENDM
dec ecx
jnz @b
ret
_nop14 ENDP
;-------------------------------------------------------------------------------
PUBLIC _nop15
_nop15 PROC
mov ecx, 400000000
ALIGN 16
@@:
REPEAT 120
DB 66h, 66h, 66h, 66h, 66h, 66h, 2Eh
DB 0Fh, 1Fh, 84h, 00h, 00h, 00h, 00h, 00h
ENDM
dec ecx
jnz @b
ret
_nop15 ENDP
;-------------------------------------------------------------------------------
PUBLIC _nop10a
_nop10a PROC
mov ecx, 400000000
ALIGN 16
@@:
REPEAT 180
DB 66h, 66h
DB 0Fh, 1Fh, 84h, 00h, 00h, 00h, 00h, 00h
ENDM
dec ecx
jnz @b
ret
_nop10a ENDP
;-------------------------------------------------------------------------------
PUBLIC _nop11a
_nop11a PROC
mov ecx, 400000000
ALIGN 16
@@:
REPEAT 164
DB 66h, 66h, 66h
DB 0Fh, 1Fh, 84h, 00h, 00h, 00h, 00h, 00h
ENDM
dec ecx
jnz @b
ret
_nop11a ENDP
;-------------------------------------------------------------------------------
PUBLIC _nop12a
_nop12a PROC
mov ecx, 400000000
ALIGN 16
@@:
REPEAT 150
DB 0Fh, 1Fh, 40h, 00h
DB 0Fh, 1Fh, 84h, 00h, 00h, 00h, 00h, 00h
ENDM
dec ecx
jnz @b
ret
_nop12a ENDP
;-------------------------------------------------------------------------------
PUBLIC _nop13a
_nop13a PROC
mov ecx, 400000000
ALIGN 16
@@:
REPEAT 138
DB 0Fh, 1Fh, 40h, 00h
DB 66h
DB 0Fh, 1Fh, 84h, 00h, 00h, 00h, 00h, 00h
ENDM
dec ecx
jnz @b
ret
_nop13a ENDP
;-------------------------------------------------------------------------------
PUBLIC _nop14a
_nop14a PROC
mov ecx, 400000000
ALIGN 16
@@:
REPEAT 129
DB 0Fh, 1Fh, 40h, 00h
DB 66h, 66h
DB 0Fh, 1Fh, 84h, 00h, 00h, 00h, 00h, 00h
ENDM
dec ecx
jnz @b
ret
_nop14a ENDP
;-------------------------------------------------------------------------------
PUBLIC _nop15a
_nop15a PROC
mov ecx, 400000000
ALIGN 16
@@:
REPEAT 120
DB 0Fh, 1Fh, 40h, 00h
DB 66h, 66h, 66h
DB 0Fh, 1Fh, 84h, 00h, 00h, 00h, 00h, 00h
ENDM
dec ecx
jnz @b
ret
_nop15a ENDP
;-------------------------------------------------------------------------------
END
C言語部のソースはコチラ
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <time.h>
extern void nop1( void );
extern void nop2( void );
extern void nop3( void );
extern void nop4( void );
extern void nop5( void );
extern void nop6( void );
extern void nop7( void );
extern void nop8( void );
extern void nop9( void );
extern void nop10( void ); extern void nop10a( void );
extern void nop11( void ); extern void nop11a( void );
extern void nop12( void ); extern void nop12a( void );
extern void nop13( void ); extern void nop13a( void );
extern void nop14( void ); extern void nop14a( void );
extern void nop15( void ); extern void nop15a( void );
int main(int argc, char*argv[]) {
if(argc < 2) {
fprintf(stderr, "Usage: test(.exe) [nops]\n");
return -1;
}
char *s = argv[1];
void (*func)(void) =
!strcmp(s, "1") ? nop1 :
!strcmp(s, "2") ? nop2 :
!strcmp(s, "3") ? nop3 :
!strcmp(s, "4") ? nop4 :
!strcmp(s, "5") ? nop5 :
!strcmp(s, "6") ? nop6 :
!strcmp(s, "7") ? nop7 :
!strcmp(s, "8") ? nop8 :
!strcmp(s, "9") ? nop9 :
!strcmp(s, "10") ? nop10 : !strcmp(s, "10a") ? nop10a :
!strcmp(s, "11") ? nop11 : !strcmp(s, "11a") ? nop11a :
!strcmp(s, "12") ? nop12 : !strcmp(s, "12a") ? nop12a :
!strcmp(s, "13") ? nop13 : !strcmp(s, "13a") ? nop13a :
!strcmp(s, "14") ? nop14 : !strcmp(s, "14a") ? nop14a :
!strcmp(s, "15") ? nop15 : !strcmp(s, "15a") ? nop15a :
NULL;
if(func == NULL) {
fprintf(stderr, "illegal option: %s\n", s);
return -1;
}
clock_t t1 = clock();
(*func)();
clock_t t2 = clock();
fprintf(stdout, "time: %.3f\n", (double)(t2 - t1) / CLOCKS_PER_SEC);
return 0;
}
メイクファイルはコチラ
target: TEST.EXE
TEST.EXE: TEST.OBJ NOPS.OBJ
link $** /DEBUG:NONE /OUT:$@
.C.OBJ:
cl /c /O2 /W4 /GS- $<
.ASM.OBJ:
ml /c $<
clean:
if exist *.OBJ del *.OBJ
if exist *.EXE del *.EXE
テスト用バッチファイルはコチラ
@echo off
for %%I in (
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
10a 11a 12a 13a 14a 15a
) do (
echo %%I
start /b /w /realtime test %%I
)
exit /b
参考文献
- x86_64 機械語入門 - githubio
- Intel 64 and IA-32 Architectures Software Developer’s Manual - intel
- nop命令のオペランド - Qiita
- 色々なNOPコード - 黒翼猫のコンピュータ日記 3rd Edition
- 何もしない長い命令、マルチバイト NOP (x86/x64) - やや低レイヤー研究所
- インテル 64 および IA-32 アーキテクチャー最適化リファレンス・マニュアル参考訳 Volume 1 - iSUS
- インテル 64 および IA-32 アーキテクチャー最適化リファレンス・マニュアル参考訳 Volume 2: 旧世代のインテル 64 および IA-32 プロセッサー・アーキテクチャー、スループット、およびレイテンシー - iSUS
- Intel、次期CPU「Alder Lake」に搭載される新コアの詳細を発表 - PC Watch
- あなたの知らないnopたち@ラボユース合宿 - slideshare
- 第221回 コンピュータアーキテクチャの話 ダイナミック電力の低減手法(4) - マイナビ
- 高性能コンピュータ技術の基礎,Hisa Ando,マイナビ出版,2011年6月
- NOP WORD PTR? - Qiita
- nop命令のオペランド - Qiita