1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

intel C++ コンパイラは QuuuuuuX なコードを吐く(仮題)

Last updated at Posted at 2025-05-25

マルチバイト NOP 命令のことです。

はじめに

プログラムをしているとき,とくに意味を持たず適当な名前を付けたい場合がある。そういう時にデフォルトで用いられる名前を メタ構文変数 - wikipedia と呼ぶらしい。日本では hoge が有名だが,英語圏では一般的に下記の順に変数名を用いるようだ。

  1. foo
  2. bar
  3. baz
  4. qux
  5. quux

ただし,必ずしも某アニメのように6番目以降 quuuxquuuuxquuuuux, 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 マルチバイトNOP命令の例(intel C++ コンパイラの場合)
長さ バイト列 アセンブリ
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 命令を確認しているので筆者の調査不足と思われる。

表2 Microsoft Visual C++ listing.inc における npad マクロ
長さ バイト列 アセンブリ
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}
表3 マルチバイト NOP 命令
1800 bytes 分の実行時間(ns)
長さ 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
表4 マルチバイト NOP 命令
1サイクルあたりの実行数(個)
長さ 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 プロセッサの結果と比較してみよう。

表5 マルチバイト NOP 命令
1800 bytes 分の実行時間(ns)
コンパイラ
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
表6 マルチバイト NOP 命令
1サイクルあたりの実行数(個)
コンパイラ
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}
表7 マルチバイト NOP 命令の総実行時間・並列実行数の比較
コンパイラ
総実行時間(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 アセンブラ部のソースはコチラ

すっごい久しぶりにアセンブラのコードを書いたわ。

NOPS.ASM
        .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言語部のソースはコチラ
TEST.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;
}
メイクファイルはコチラ
Makefile
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
テスト用バッチファイルはコチラ
GOGO.CMD
@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

参考文献

  1. x86_64 機械語入門 - githubio
  2. Intel 64 and IA-32 Architectures Software Developer’s Manual - intel
  3. nop命令のオペランド - Qiita
  4. 色々なNOPコード - 黒翼猫のコンピュータ日記 3rd Edition
  5. 何もしない長い命令、マルチバイト NOP (x86/x64) - やや低レイヤー研究所
  6. インテル 64 および IA-32 アーキテクチャー最適化リファレンス・マニュアル参考訳 Volume 1 - iSUS
  7. インテル 64 および IA-32 アーキテクチャー最適化リファレンス・マニュアル参考訳 Volume 2: 旧世代のインテル 64 および IA-32 プロセッサー・アーキテクチャー、スループット、およびレイテンシー - iSUS
  8. Intel、次期CPU「Alder Lake」に搭載される新コアの詳細を発表 - PC Watch
  9. あなたの知らないnopたち@ラボユース合宿 - slideshare
  10. 第221回 コンピュータアーキテクチャの話 ダイナミック電力の低減手法(4) - マイナビ
  11. 高性能コンピュータ技術の基礎,Hisa Ando,マイナビ出版,2011年6月
  12. NOP WORD PTR? - Qiita
  13. nop命令のオペランド - Qiita
1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?