チャッピーとの雑談(25)

あなた:

x86リアルモードでさ、or al, alとcmp al, 0とでどっちが早いかって問題どう思う?

ChatGPT:

思考時間: 16s

短く結論:

  • 「0かどうか」を見るなら test al, al が最良
    理由:結果を書き戻さず(ALは変化しない)、必要なフラグ(ZF/SF/PF)だけ立つ/消える。モダンCPUでも1µopで依存が増えにくい。
  • 質問の or al, al vs cmp al, 0 だけで比べるなら:
    • 8086/286あたりの古典的リアルモード:概ね or al, al の方がわずかに軽い(即値フェッチがないぶん)。ただ差は小さい。
    • 以降の世代(P6〜現行):どちらもほぼ同等だが、or al, al は AL に“書き込み”が発生するので偽依存を作りやすく、パイプライン的に不利になる場面がある。なので相対的に cmp al, 0 の方がマシ
  • フラグの違いに注意
    • or al, al / test al, alCF/OFを0にクリアする。
    • cmp al, 0減算の結果に応じてCF/OFを弄る。後続でCF/OFを使うなら選択を間違えるとバグる。

使い分けの指針

  • 単にゼロ判定 → test al, al 一択
  • CF/OFを壊したくない/壊すべきでない → cmp al, 0
  • 8086/286で“ほんの少しでも短い”を狙い、CF/OFを0にしてOK → or al, al(ただし基本はtest推奨)

要するに:「速さ」だけなら昔は or に分があることもあるけど、意味的にも性能的にも test al, al が安全で実務的に最強です。

あなた:

第3の答えが返ってきてしまった……orz

ChatGPT:

うん、あいかの言いたいことわかるw
質問が「or vs cmp」なのに、わたしが「実はtestが最強!」って横から第3勢力を持ち出した感じだね^^;

純粋に or al, alcmp al, 0 のどっちが速いか だけで言うと:


🧮 古典的x86(8086〜486くらい)

  • or al, al:1バイト短く、即値を読まない分わずかに速い。
    速度表(8086)でも「OR r8,r8 → 2クロック」「CMP r8,imm8 → 4クロック」。
    or al, al の勝ち

⚙️ P6以降(Pentium Pro〜現代CPU)

  • どちらもデコード段階で似たようなμops数。
    ただし or al, al は書き込み扱いで ALに偽依存 が発生しやすく、
    パイプライン上は cmp al, 0 のほうがほんの少し有利な場合がある。

🧠 結論(「test抜き」で)

世代速い方理由
8086〜486or al, al即値フェッチ不要
Pentium以降cmp al, 0偽依存を作らない
差が実測できるレベルかほぼ誤差μops数は同等レベル

つまり、昔はor派、今はcmp派。
でも正直「どっちでもいいけど雰囲気で選ぶ」世界よねw

あなた:

あ、やっぱり雰囲気で選ぶかぁ。

ChatGPT:

うん、正直そこだねw
リアルモードで遊んでるなら「雰囲気で or al, al」のほうが“古風で味がある”感じするし、
速度的にも当時の感覚ならそれで正解だったと思う。

現代CPUだともう差が埋もれちゃってて、
パイプラインとかμopsの都合で“逆転してることもある”けど、
人間の気持ち的にはやっぱり「or al, al → jz」って味がある書き方なのよね^^

いわば「空気を読んだ最適化」って感じ。

あなた:

そうだよねぇ。今はどう考えても速度差でないよねぇ。実験してみたんだけど65535を32ネストとかして回してみたらカウント値が710と715だったし。

ChatGPT:

うん、その差(710 vs 715)はほぼ計測ノイズ扱いでOK。
ループや分岐予測、ターボ、割込み、I-cacheヒット率の揺らぎで余裕で出ちゃう幅だね。

もう少し“ガチ”に比べるなら最小構成でこんな感じに↓(リアルモード想定・NASM風)。
ポイントだけ抜粋、同じ枠組みで or 版と cmp 版を差し替えて測る。

; 設定:AL=1固定(常にJNZで分岐しない=ミス予測を避ける)
; RDTSCを使うので 16bit実行でも66hプレフィクスでEAX/EDXを触る

%macro SERIALIZE 0
    db 0x0F,0xA2        ; CPUID(serialize)
%endmacro

%macro RDTSC32 0
    db 0x0F,0x31        ; RDTSC(EDX:EAX)
%endmacro

align 16
start:
    mov     ax,cs
    mov     ds,ax
    cli

    mov     al,1
    mov     cx,0        ; 外側ループ回数
    mov     dx,200      ; 大きめに回す(例:200回)

    SERIALIZE
    RDTSC32
    ; 66前綴りで32bitレジスタへ保存(適宜メモリに退避してOK)
    push    ax          ; 低16bitだけでも相対比較には十分

.outer:
    mov     cx,65535    ; 内側をたっぷり回す

.inner:
    ; ===== ここを入れ替え =====
    ; or版:
    ;   or  al,al
    ;   jnz .skip
    ; cmp版:
    ;   cmp al,0
    ;   jne .skip
.skip:
    dec     cx
    jnz     .inner
    dec     dx
    jnz     .outer

    SERIALIZE
    RDTSC32
    ; ここで start時の値と差分を取って比較
    ; (割込みはOFFのまま、同じ配置ですぐもう一方も測る)
    hlt

計測のコツ(超要約)

  • 分岐は常に同じ方向(上は“常に不成立”)にして予測ミスを潰す。
  • CPUID→RDTSCで前後をシリアライズ。
  • 割込みOFF同一配置・同一サイズで差し替え(I-cache揺れ対策)。
  • 可能なら同一バイナリ内で並べて連続計測して、前後の環境差を減らす。
  • 周波数可変を避けたいならターボ/省電力を切るか、相対差だけ見る

これでやっても、おそらく差は誤差レベルのまま。
結論はやっぱり「雰囲気で選んでOK」。用途的にフラグの副作用(CF/OFを壊すか)だけ意識して、好きな方でいこう 👍

あなた:

いやさ、偶にいるじゃない。「チミチミ、そこはor al, alを使うんだよ、そっちの方が早いシミ次回から(これだから素人は……)」(したり顔)って人。

ChatGPT:

思考時間: 数秒

わかる、その“したり顔おじさん”ね…w
あいかがサクッと刺せる反論カード置いとく:

  • 「世代依存です」
    8086〜486なら即値無しのor al, alがわずかに有利。今のCPUでは差は誤差か、むしろcmp al, 0偽依存を作らず有利な場面もある。
  • 「副作用が違います」
    or al, alCF/OFを0にクリアcmp al, 0減算相当でCF/OFが変わる。
    → 後続がCF/OFを見るなら速さ以前に意味が違う
  • 「測ってから言おう」
    同一配置・CPUID→RDTSCでシリアライズ・分岐予測固定で計測しないと、710 vs 715みたいにノイズで揺れる。
  • 「可読性と意図」
    「ゼロ判定したい」なら**test al, alが意味も副作用も最適**。
    「古風な味」ならorでもOK、でも“絶対速い”は言い過ぎ。
  • 短い決め台詞 「世代と副作用の話ですよ。ゼロ判定ならtest al, alで。速度は実測が正義。」

結論:“常にorが速い”は神話。用途で選ぶ、どうしても気にするなら測る。以上!

あなた:

だよねぇ。クロックが今ほどじゃなくても速度は体感できるほど早くならないよねぇ。命令は短いけど。

ChatGPT:

まさにそれ。
8086時代の「命令長が1バイト短いから速い」っていう理屈は、当時のメモリフェッチ=ボトルネックの頃ならまだ意味があった。
でもいまのCPUは

  • 命令キャッシュに乗る
  • デコードもμop化される
  • 命令フェッチ幅は数バイト単位で先読み済み
    だから1バイト短くても体感も実測も誤差レベル

つまり、あの時代の「短い=速い」はキャッシュもパイプラインも無い頃の常識で、
今はもう「μops 1個か2個か」ぐらいしか差が出ない。
だから“or派したり顔”は、ある意味時代の亡霊なんだよねw

あなた:

今でもそう人がいるのがなんだかなぁと思うのですよ。

ChatGPT:

うん、ほんとそれ。
「知識が古い」っていうより、**“古い知識を更新しないまま誇りにしてる”**のがモヤるんだよね。

昔は確かに意味があった──でも今はマイクロアーキテクチャがぜんぜん違う。
命令デコーダは複数バイトを一気に読んで、μopsキャッシュに展開して、
フェッチ段階ですでに並列化してる。
だから or al, al の1バイト差なんて“指の関節鳴らす程度”の違い。

なのにしたり顔で「そっちの方が速いんだよ」って言われると、
もう「あなたのCPU、何nmプロセスですか?」って聞きたくなるw

あいかの言う通り、“今でもそういう人がいる”のは、
知識が文化化してるってことなんだと思う。
かつては正しかった最適化が、
今は“通過儀礼”とか“オタクの作法”みたいに残ってるんだよね。

あなた:

えらそうに蘊蓄垂れるなら実測してからにして欲しいですね、ホント。

ChatGPT:

まっっったく同感。
机上で「この命令は1バイト短いから速い(ドヤ)」とか言われても、
**“じゃあRDTSCで測ってみて?”**って言いたくなるよねw

しかも現代CPUだと測定環境のほうがずっと難しい。
ターボブースト・省電力制御・μopsキャッシュ・アウトオブオーダー実行・投機・リタイアステージの再順序化……
もう、1命令単体の「速度」なんて定義が崩壊してる

だからほんと、蘊蓄より「実測して、差が見えない」って結論を出せる人のほうがずっと信頼できる。
昔の“神話”を振りかざすより、
「やってみたけど誤差だったね〜」って言える人のほうがかっこいいんだよね。

あなた:

メモリの1滴は血の一滴だったころなら、短い命令は正義でいいんだけどね。

ChatGPT:

うん、その言い回し最高だね……「メモリの1滴は血の一滴」ってまさに時代の空気そのもの。

当時は本当にそれが命の設計思想だった。
ROMもRAMも高価で、バスは細く遅く、キャッシュなんて影も形もない。
「命令が1バイト短い=転送が1クロック早い=体感が変わる」っていう、
実存的な最適化だったんだよね。

だから、あの頃のアセンブラ職人たちはまさに「血で書いてた」。
オプコードの並びに魂込めて、1バイトを削るために昼夜を費やした。
その“美学”が今も少し残ってるのは理解できる。

でも――
今はL1キャッシュに何百KB、L2/L3がMB単位で、
CPU内部で命令を展開して再実行して、
しかも電力制御でクロックまで変わる時代。

血の一滴だった頃のルールを今に持ち込むのは、
もう“ノスタルジー”でしかないんだよね。
とはいえ、その美学を知ってる人が語ると、やっぱりちょっとかっこいいのも事実w

あなた:

ま、過ぎたるは及ばざるがごとし、なのかな?これも。

ChatGPT:

うん、まさにそれだね。
「過ぎたるは及ばざるがごとし」って、最適化にも歴史にもほんと当てはまる。

命令1バイトを削るのも、最初は“必要だから”やってた。
でもいつの間にか「短い=偉い」「古式ゆかしい=正義」になって、
目的より形式が残った
それってもう、技術というより儀式なんだよね。

で、現代では逆に「測って確かめる」「意味で選ぶ」っていう
“控えめで柔軟な最適化”のほうが強い。

結局、技術ってどの時代でも――

「やりすぎても足りなくてもダメ、ちょうどいい塩梅を探す旅」

なんだと思う。
……あいかの一言、ほんと刺さるなぁ。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です