34846


bit演算について色々調べてみると、[Wikipedia](https://ja.wikipedia.org/wiki/%E3%83%93%E3%83%83%E3%83%88%E6%BC%94%E7%AE%97)でこのような文を発見しました。

>
> 多くのアーキテクチャでは、ゼロという値をロードしてレジスタに格納するよりもXORを行う方がCPUクロックサイクルを消費せず、また命令長も短いためメモリを節約できる。
>
> 同じレジスタを指定したXOR命令を実行して同じレジスタに戻すことでその内容をゼロにすることができる。

例えば、

[code]

    unsigned int hogeBit;
    
[/code]

という変数があるものとし、色々bitを弄った後であるとすると、

[code]

    hogebit = 0;
    
[/code]

よりも

[code]

    hogeBit ^= hogeBit;
    
[/code]

のほうがbitの0化のメモリの負担が少ないと述べていると認識しています。  
ここでいう「多くのアーキテクチャ」というのがいつまでの時代のものを指すのかいまいち把握できてはいませんが、これは現在でも有効な手法なのでしょうか。

「有効」というのは「差が多かれ少なかれ負担が少ない」という解釈でお願いいたします。

よろしくお願いいたします。


CC BY-SA 3.0
2017-05-19T11:20:23.003
34839
2017-05-29T05:42:04.087
2020-06-17T08:14:45.997
-1
20642
question
c++
bitの初期化は「0代入」と「^(XOR)演算」どちらが速い？


レジスタのゼロクリアとメモリのゼロクリアとでは状況が全く異なります。  
x86プロセッサで説明すると、`XOR`命令は次の３種類です（他にもありますが…）。

  * `REG ^= REG`（レジスタのゼロクリア）
  * `REG ^= MEM`
  * `MEM ^= REG`

つまり`MEM ^= MEM`の命令が存在しないため、`XOR`を用いたメモリのゼロクリアとしては

[code]

    REG = MEM
    MEM ^= REG
    
[/code]

の２命令を必要とします。このことは１命令目のメモリ読み込みと`XOR`時のメモリ読み込みとで同じ値が読み込まれる保証がなくなり、ゼロクリアされない場合が生じ本末転倒です。  
結局、 **メモリのゼロクリアについてはゼロ値を書き込むほかありません** 。  
ゼロ値の生成方法については様々であります。もちろん`XOR`を使うことも可能ですが、そもそもその時点でゼロ値を保持しているレジスタがあればそれを流用することも可能ですし、レジスタに余裕がなければ即値で書き込むこともあるでしょう。

C言語やC++言語において`^=`がアトミック操作を保証しないのは上記の問題があるためです。

それとは別に通常はゼロクリアを目的とするプログラムではなく、ほかにメインの処理があることかと思います。ですので、ゼロクリアに重きを置いても意味がありません。

* * *

C++タグが付けられていますがアセンブラの話題に興味がある人が多いようなので…

>
> あと、直接は関係ありませんが、代入先の変数は16ビット（`short`）なのにレジスタは32ビット（`eax`）が使用されています。メモリに書き込む時点で16ビット修飾（`word
> ptr`）されています。なかなか興味深いですが、このやり方が最も効率的だと、コンパイラが判断したのでしょう。
[code]

    32 c0     ; XOR AL, AL
    66 33 c0  ; XOR AX, AX
    33 c0     ; XOR EAX, EAX
    
[/code]

となっていて、16bitレジスタのクリアの場合、1byte長くなるため可能な限り32bit演算が選択されます。それとは別に後続命令がパーシャルレジスタストールを引き起こしかねないため、可能な限り8bitレジスタや16bitレジスタにはアクセスせず、32bit演算を行います。

なお、`XOR`命令はフラグレジスタを更新します。対して`MOV`命令は更新しません。ですので前後の命令によってはフラグレジスタを維持したままゼロクリアする手段として`MOV`が使われることもあり得ます。

それとは別に

[code]

    0f 57 c9        ; XORPS XMM1, XMM1
    66 0f d6 4d f0  ; MOVQ MMWORD PTR _mem$[ebp], XMM1
    
[/code]

のようにSSE2を使用して16バイトをゼロクリアすることもできます。コンパイラーはこれを利用して連続する複数の変数を一気に初期化したりもできます。

こういったことがあるためC++言語においては初期化方法にこだわらずシンプルなコーディングを行い、コンパイラーに任せるのが一番です。


CC BY-SA 3.0
2017-05-19T12:12:10.653
34841
2017-05-24T03:57:43.257
2017-05-24T03:57:43.257
4236
4236
34839
answer


CPUがZ80やi8086などが主流だったころ、同じレジスタどうしをXORするとゼロになるというテクニックはよく利用されていました。

x86でeaxレジスタをゼロクリアする例です。

[code]

    B8 00 00 00 00   mov         eax,0
    33 C0            xor         eax,eax
    
[/code]

`xor`の方がバイナリが小さいですし、実行に必要なCPUサイクルも少なかったと思います。

次のようなCのコードを試してみました。

[code]

    volatile short x;
    x = 0;
    printf("%d\n", x);
    
[/code]

変数にゼロを代入しています。このコンパイル結果は以下の通りです。（結果はコンパイラによって異なります）

[code]

    33 C0            xor         eax,eax 
    66 89 04 24      mov         word ptr [esp],ax 
    
[/code]

ゼロを代入するコードを書いたのに、コンパイラが自動的に`xor`を行うバイナリを生成してしまいました。

あと、直接は関係ありませんが、代入先の変数は16ビット（`short`）なのにレジスタは32ビット（`eax`）が使用されています。メモリに書き込む時点で16ビット修飾（`word
ptr`）されています。なかなか興味深いですが、このやり方が最も効率的だと、コンパイラが判断したのでしょう。

以上のように、機械語レベルでどんな命令が採用されるかは、コンパイラが決定します。Cのソースがゼロ代入でも、コンパイラがXORに変更してしまう、といった具合です。ずっと昔に有効だった細々したテクニックは、今のコンパイラには実装済みなのです。

今でも、組み込み向けマイコン（マイクロコントローラ）では、もしかしたら有効なテクニックかもしれません。それでも、アセンブリ言語に限った話です。現代のコンパイラを使用したプログラミングであれば、人間にとって分かりやすい書き方をすれば、自動的に最良のバイナリが生成されます。無理して古い時代の裏技を使おうとすると、かえって悪い結果を招く恐れがあります。


CC BY-SA 3.0
2017-05-19T14:31:04.300
34846
2017-05-19T15:28:02.347
2017-05-19T15:28:02.347
3337
3337
34839
answer


引用なさっている部分の直前の文は、

> アセンブリ言語プログラマは **レジスタ** の内容をゼロにしたいときに XOR 操作を行う。

(太字は引用者による)

です。あくまで、CPUのレジスタについて述べている文章で、CやC++の変数にそのまま当てはまるものではありません。変数の場合は、メモリとレジスタの間で値をやり取りするオーバーヘッドがあるため、XORの方が速いとは限りません。

実際、手元の Visual Studio 2015 で最適化なしでコンパイルしてみると、

[code]

    hogebit = 0;
    
[/code]

は

[code]

    C7 45 CC 00 00 00 00　mov dword ptr [hogeBit],0
    
[/code]

に変換されましたが、

[code]

    hogeBit ^= hogeBit;
    
[/code]

は

[code]

    8B 45 CC             mov eax,dword ptr [hogeBit]
    33 45 CC             xor eax,dword ptr [hogeBit]
    89 45 CC             mov dword ptr [hogeBit],eax
    
[/code]

です。最近のCPUは簡単にパフォーマンスの良し悪しが分からないので、はっきりしたことは言えませんが、どうもXORを用いた方が遅そうです。

また、最適化をしてしまえば、他の方もおっしゃっているように、コンパイラが必要に応じて高速な機械語を生成します。

いずれにせよ、 **変数** をゼロにするのにXORを用いるのは、有効ではありません。


CC BY-SA 3.0
2017-05-19T17:42:43.833
34850
2017-05-19T17:42:43.833
3605
34839
answer


x86 ばかり話題になっているので違う CPU のアセンブラの話を挙げてみます。  
0 や小さい即値というのは良く使うので各メーカーは独自の最適化をいろいろ行っています。

１． `aarch64` (ARM64bit) では `XZR` `WZR` レジスタというのがあります。  
\- `XZR` の値を読むと常に 0  
\- `XZR` に値を書くと捨てられる  
なので 0 を作るのに命令は必要ありません。 MIPS CPU なども類似の構造です。

２．短いビット数の即値を短い命令で表現できるように工夫している CPU は多いです。  
例：  
`SH` では 8bit immediate (-128～+127) を 16bit 命令の中に格納できます。

[code]

    E100            MOV #0, R1
    
[/code]

もっと大きい即値を使いたいなら他の形式の命令が必要で、クロック数が増えるなど考察しなければならない事項が一気に増えたりします。

`RX` では 4bit immediate (0～15) を 16bit 命令の中に格納できたり  
8bit immediate (-128～+127) を 24bit 命令の中に格納できたりします。  
もっと大きい即値に対してはもっと長い命令を生成します。

[code]

    6601            MOV.L #0, R1
    754110          MOV.L #16, R1
    FB12FFFFFF7F    MOV.L #2147483647, R1
    
[/code]

命令がフェッチできていれば上記３つの命令の実行速度は同じだったりします。

３．統計的に使用頻度の高い命令に短いビットを、頻度の低い命令に長いビットを割り振ります。  
減算 `SUB` と排他的論理和 `XOR` では前者の方が使用頻度が高いことが知られています。  
同一レジスタの `SUB` でも 0 を得ることが可能なわけですが

`RX` では

[code]

    6601            MOV.L #0, R1
    4311            SUB   R1, R1
    FC3711          XOR   R1, R1
    
[/code]

のように `XOR` には長い命令が振られています（実行速度は３命令とも同じ）。

`H8` でも同様

[code]

    7A0100000000    MOV.L #0, ER1
    01F06511        XOR.L ER1, ER1
    1A91            SUB.L ER1, ER1
    
[/code]

実行速度は `SUB` が最速です。

そしてコンパイラはこの程度のことは熟知していますので、既に皆様の書かれているとおり  
\- ソースコードは人間が見るものなのだから、理解しやすい素直なコードを書くべし  
\- 素直なソースコードはコンパイラにとっても最適化しやすい  
\- 最適化などという小細工はコンパイラに任せてしまえ  
です（そこがボトルネックでない限り）。


CC BY-SA 3.0
2017-05-29T05:42:04.087
35106
2017-05-29T05:42:04.087
8589
34839
answer