1. ホーム
  2. c++

ゼロと比較する際の int 演算子 != および == について

2023-09-11 16:56:09

質問

ゼロか非ゼロかを調べるのに、!=と==は最速の方法ではないことがわかりました。

bool nonZero1 = integer != 0;
xor eax, eax
test ecx, ecx
setne al

bool nonZero2 = integer < 0 || integer > 0;
test ecx, ecx
setne al

bool zero1 = integer == 0;
xor eax, eax
test ecx, ecx
sete al

bool zero2 = !(integer < 0 || integer > 0);
test ecx, ecx
sete al

コンパイラ VC++ 11 最適化フラグ。/O2 /GL /LTCG

これは x86-32 のアセンブリ出力です。x86-32 と x86-64 の両方で、両方の比較の 2 番目のバージョンは ~12% 速かったです。ただし、x86-64 では、命令は同じでしたが (最初のバージョンは 2 番目のバージョンとまったく同じに見えました)、2 番目のバージョンの方がまだ高速でした。

  1. なぜコンパイラーは x86-32 上でより高速なバージョンを生成しないのでしょうか。
  2. アセンブリの出力が同じなのに、なぜ x86-64 では 2 番目のバージョンがまだ高速なのですか?

EDIT: ベンチマークコードを追加しました。ZERO:1544ms, 1358ms NON_ZERO:1544ms, 1358ms http://pastebin.com/m7ZSUrcP または http://anonymouse.org/cgi-bin/anon-www.cgi/http://pastebin.com/m7ZSUrcP

注:単一のソースファイルでコンパイルした場合、main.asmがかなり大きくなるので、これらの関数の場所を特定するのは不便でしょう。私は、zero1, zero2, nonZero1, nonZero2 を別のソースファイルにしていました。

EDIT2: VC++11 と VC++2010 の両方をインストールしたどなたか、ベンチマーク コードを実行し、タイミングを投稿していただけませんか。それは確かに VC++11 のバグかもしれません。

どのように解決するのですか?

<ブロッククオート

EDIT: OPのアセンブリリストを見て、私のコードを見てみました。私は、これが VS2011 の一般的なバグであるかどうか とは思えません。これは、単にOPのコードのための特別なケースのバグかもしれません。OP のコードをそのまま clang 3.2, gcc 4.6.2, VS2010 で実行したところ、すべてのケースで 最大差分 は ~1% でした。

ソースをコンパイルし、適切な修正を加えて、私の ne.c ファイルおよび /O2/GL のフラグを設定します。以下はそのソースです。

int ne1(int n) {
 return n != 0;
 }

 int ne2(int n) {
 return n < 0 || n > 0;
 }

 int ne3(int n) {
 return !(n == 0);
 }

int main() { int p = ne1(rand()), q = ne2(rand()), r = ne3(rand());}

と対応するアセンブリを

    ; Listing generated by Microsoft (R) Optimizing Compiler Version 16.00.30319.01 

    TITLE   D:\llvm_workspace\tests\ne.c
    .686P
    .XMM
    include listing.inc
    .model  flat

INCLUDELIB OLDNAMES

EXTRN   @__security_check_cookie@4:PROC
EXTRN   _rand:PROC
PUBLIC  _ne3
; Function compile flags: /Ogtpy
;   COMDAT _ne3
_TEXT   SEGMENT
_n$ = 8                         ; size = 4
_ne3    PROC                        ; COMDAT
; File d:\llvm_workspace\tests\ne.c
; Line 11
    xor eax, eax
    cmp DWORD PTR _n$[esp-4], eax
    setne   al
; Line 12
    ret 0
_ne3    ENDP
_TEXT   ENDS
PUBLIC  _ne2
; Function compile flags: /Ogtpy
;   COMDAT _ne2
_TEXT   SEGMENT
_n$ = 8                         ; size = 4
_ne2    PROC                        ; COMDAT
; Line 7
    xor eax, eax
    cmp eax, DWORD PTR _n$[esp-4]
    sbb eax, eax
    neg eax
; Line 8
    ret 0
_ne2    ENDP
_TEXT   ENDS
PUBLIC  _ne1
; Function compile flags: /Ogtpy
;   COMDAT _ne1
_TEXT   SEGMENT
_n$ = 8                         ; size = 4
_ne1    PROC                        ; COMDAT
; Line 3
    xor eax, eax
    cmp DWORD PTR _n$[esp-4], eax
    setne   al
; Line 4
    ret 0
_ne1    ENDP
_TEXT   ENDS
PUBLIC  _main
; Function compile flags: /Ogtpy
;   COMDAT _main
_TEXT   SEGMENT
_main   PROC                        ; COMDAT
; Line 14
    call    _rand
    call    _rand
    call    _rand
    xor eax, eax
    ret 0
_main   ENDP
_TEXT   ENDS
END

ne2() を使用した < , >|| 演算子は 明らかに の方がより高価です。 ne1()ne3() を使用するもので ==!= という演算子は、それぞれ逆で等価です。

Visual Studio 2011 は ベータ版で . 私はこれをバグと見なします。他の 2 つのコンパイラを使った私のテストでは、すなわち gcc 4.6.2 clang 3.2 というように O2 最適化スイッチによって、私の Windows 7 マシンで行った 3 つのテストすべてにおいて、まったく同じアセンブリが得られました。以下はその要約です。

$ cat ne.c

#include <stdbool.h>
bool ne1(int n) {
    return n != 0;
}

bool ne2(int n) {
    return n < 0 || n > 0;
}

bool ne3(int n) {
    return !(n != 0);
}

int main() {}

はgccで収まる。

_ne1:
LFB0:
    .cfi_startproc
    movl    4(%esp), %eax
    testl   %eax, %eax
    setne   %al
    ret
    .cfi_endproc
LFE0:
    .p2align 2,,3
    .globl  _ne2
    .def    _ne2;   .scl    2;  .type   32; .endef
_ne2:
LFB1:
    .cfi_startproc
    movl    4(%esp), %edx
    testl   %edx, %edx
    setne   %al
    ret
    .cfi_endproc
LFE1:
    .p2align 2,,3
    .globl  _ne3
    .def    _ne3;   .scl    2;  .type   32; .endef
_ne3:
LFB2:
    .cfi_startproc
    movl    4(%esp), %ecx
    testl   %ecx, %ecx
    sete    %al
    ret
    .cfi_endproc
LFE2:
    .def    ___main;    .scl    2;  .type   32; .endef
    .section    .text.startup,"x"
    .p2align 2,,3
    .globl  _main
    .def    _main;  .scl    2;  .type   32; .endef
_main:
LFB3:
    .cfi_startproc
    pushl   %ebp
    .cfi_def_cfa_offset 8
    .cfi_offset 5, -8
    movl    %esp, %ebp
    .cfi_def_cfa_register 5
    andl    $-16, %esp
    call    ___main
    xorl    %eax, %eax
    leave
    .cfi_restore 5
    .cfi_def_cfa 4, 4
    ret
    .cfi_endproc
LFE3:

で、clangで

    .def     _ne1;
    .scl    2;
    .type   32;
    .endef
    .text
    .globl  _ne1
    .align  16, 0x90
_ne1:
    cmpl    $0, 4(%esp)
    setne   %al
    movzbl  %al, %eax
    ret

    .def     _ne2;
    .scl    2;
    .type   32;
    .endef
    .globl  _ne2
    .align  16, 0x90
_ne2:
    cmpl    $0, 4(%esp)
    setne   %al
    movzbl  %al, %eax
    ret

    .def     _ne3;
    .scl    2;
    .type   32;
    .endef
    .globl  _ne3
    .align  16, 0x90
_ne3:
    cmpl    $0, 4(%esp)
    sete    %al
    movzbl  %al, %eax
    ret

    .def     _main;
    .scl    2;
    .type   32;
    .endef
    .globl  _main
    .align  16, 0x90
_main:
    pushl   %ebp
    movl    %esp, %ebp
    calll   ___main
    xorl    %eax, %eax
    popl    %ebp
    ret

私の提案は、これをバグとして マイクロソフト コネクト .

注:対応するC++コンパイラを使用しても、ここでは大きな変化がないと思うので、Cソースとしてコンパイルしました。