Pertanyaan int operator! = dan == ketika membandingkan dengan nol


Saya telah menemukan itu! = Dan == bukan cara tercepat untuk menguji untuk nol atau tidak nol.

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

Compiler: VC ++ 11 Bendera pengoptimalan: / O2 / GL / LTCG

Ini adalah output perakitan untuk x86-32. Versi kedua dari kedua perbandingan itu ~ 12% lebih cepat pada keduanya x86-32 dan x86-64. Namun, pada x86-64 instruksi itu identik (versi pertama tampak persis seperti versi kedua), tetapi versi kedua masih lebih cepat.

  1. Mengapa compiler tidak menghasilkan versi yang lebih cepat di x86-32?
  2. Mengapa versi kedua masih lebih cepat pada x86-64 ketika output assembly identik?

EDIT: Saya telah menambahkan kode pembandingan. ZERO: 1544ms, 1358ms NON_ZERO: 1544ms, 1358ms http://pastebin.com/m7ZSUrcP atau http://anonymouse.org/cgi-bin/anon-www.cgi/http://pastebin.com/m7ZSUrcP

Catatan: Mungkin tidak nyaman untuk menemukan fungsi-fungsi ini ketika dikompilasi dalam satu file sumber, karena main.asm berjalan cukup besar. Saya memiliki nol1, nol2, nonZero1, nonZero2 dalam file sumber terpisah.

EDIT2: Apakah seseorang dengan VC ++ 11 dan VC ++ 2010 yang diinstal menjalankan kode pembandingan dan memposting timing? Mungkin memang bug di VC ++ 11.


75
2018-05-31 17:50


asal


Jawaban:


EDIT: Melihat daftar perakitan OP untuk kode saya. Saya ragu ini bahkan a bug umum dengan VS2011 sekarang. Ini mungkin hanya bug kasus khusus untuk kode OP. Saya menjalankan kode OP apa adanya dengan clang 3.2, gcc 4.6.2 dan VS2010 dan dalam semua kasus perbedaan maks berada di ~ 1%.

Baru saja mengumpulkan sumber dengan modifikasi yang sesuai untuk saya ne.c file dan /O2 dan /GL bendera. Inilah sumbernya

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());}

dan majelis yang sesuai:

    ; 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() yang menggunakan <, > dan || operator adalah jelas lebih mahal. ne1() dan ne3() yang menggunakan == dan != operator masing-masing, terser dan setara.

Visual Studio 2011 adalah dalam versi beta. Saya akan menganggap ini sebagai bug. Tes saya dengan dua kompiler lainnya yaitu gcc 4.6.2 dan clang 3.2, dengan O2 switch optimasi menghasilkan perakitan yang sama persis untuk semua tiga tes (yang saya punya) pada kotak Windows 7 saya. Berikut ringkasannya:

$ 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() {}

hasil dengan 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:

dan dengan dentang:

    .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

Saran saya adalah file ini sebagai bug dengan Microsoft Connect.

Catatan: Saya mengumpulkan mereka sebagai sumber C karena saya tidak berpikir menggunakan kompiler C ++ yang sesuai akan membuat perubahan yang signifikan di sini.


19
2018-05-31 18:10



Ini adalah pertanyaan yang bagus, tapi saya pikir Anda telah menjadi korban analisis ketergantungan kompiler.

Compiler hanya harus membersihkan bit yang tinggi eax sekali, dan mereka tetap jelas untuk versi kedua. Versi kedua harus membayar harga xor eax, eax kecuali bahwa analisis kompilator membuktikan itu telah ditinggalkan oleh versi pertama.

Versi kedua mampu "menipu" dengan mengambil keuntungan dari pekerjaan yang dilakukan kompilator dalam versi pertama.

Bagaimana Anda mengukur waktu? Apakah itu "(versi satu, diikuti oleh versi dua) dalam satu lingkaran", atau "(versi satu dalam satu lingkaran) diikuti oleh (versi dua dalam satu lingkaran)"?

Jangan melakukan kedua tes dalam program yang sama (sebagai gantinya mengkompilasi ulang untuk setiap versi), atau jika Anda melakukannya, uji "versi A pertama" dan "versi B pertama" dan lihat apakah yang pertama datang adalah membayar penalti.


Ilustrasi kecurangan:

timer1.start();
double x1 = 2 * sqrt(n + 37 * y + exp(z));
timer1.stop();
timer2.start();
double x2 = 31 * sqrt(n + 37 * y + exp(z));
timer2.stop();

Jika timer2 durasi kurang dari timer1 durasi, kami tidak menyimpulkan bahwa mengalikan dengan 31 lebih cepat daripada mengalikan dengan 2. Sebaliknya, kami menyadari bahwa kompilator melakukan analisis subkunci umum, dan kode menjadi:

timer1.start();
double common = sqrt(n + 37 * y + exp(z));
double x1 = 2 * common;
timer1.stop();
timer2.start();
double x2 = 31 * common;
timer2.stop();

Dan satu-satunya yang terbukti adalah bahwa mengalikan dengan 31 lebih cepat daripada komputasi common. Yang tidak mengherankan sama sekali - perkalian jauh lebih cepat daripada sqrt dan exp.


121
2018-05-31 17:58