операторы int! = и == при сравнении с нулем

Я обнаружил, что! = и == - не самые быстрые способы проверки на ноль или ненулевое значение.

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. Вторые версии обоих сравнений были на 12% быстрее как на x86-32, так и на x86-64. Тем не менее, в x86-64 инструкции были идентичны (первые версии выглядели точно так же, как и вторые версии), но вторые версии были еще быстрее.

  1. Почему компилятор не генерирует более быструю версию на x86-32?
  2. Почему вторые версии все еще быстрее на x86-64, когда выходные данные сборки идентичны?

РЕДАКТИРОВАТЬ: я добавил код тестирования. Ноль: 1544 мс, 1358 мс NON_ZERO: 1544 мс, 1358 мс http://pastebin.com/m7ZSUrcP или же http://anonymouse.org/cgi-bin /anon-www.cgi/http://pastebin.com/m7ZSUrcP

Примечание. Вероятно, неудобно размещать эти функции при компиляции в одном исходном файле, потому что main.asm имеет довольно большой размер. У меня были ноль1, ноль2, ноль ноль1, ноль ноль2 в отдельном исходном файле.

EDIT2. Может ли кто-нибудь с установленными VC ++ 11 и VC ++ 2010 выполнить код тестирования и опубликовать время? Это действительно может быть ошибка в VC ++ 11.

75 голосов | спросил NFRCR 31 Maypm12 2012, 21:50:54

2 ответа


0
  

РЕДАКТИРОВАТЬ: увидел список сборок 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   @[email protected]: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 находится в бета-версии . Я бы посчитал это ошибкой. Мои тесты с двумя другими компиляторами, а именно gcc 4.6.2 и clang 3.2 , с O2

$ 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:

и с лязгом:

    .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

Я бы посоветовал сообщить об этом как об ошибке в Microsoft Connect .

Примечание. Я скомпилировал их как исходный код C, так как не думаю, что использование соответствующего компилятора C ++ внесло бы здесь существенные изменения.

ответил dirkgently 31 Maypm12 2012, 22:10:10
0

Это отличный вопрос, но я думаю, что вы стали жертвой анализа зависимостей компилятора.

Компилятор должен только очистить старшие биты eax один раз, и они остаются чистыми для второй версии. Вторая версия должна будет заплатить цену xor eax, eax, за исключением того, что анализ компилятора подтвердил, что первая версия была очищена.

Вторая версия способна "обмануть", воспользовавшись преимуществами работы, выполненной компилятором в первой версии.

Как вы измеряете время? За «(первая версия, затем вторая версия) в цикле» или «(первая версия в цикле) после (вторая версия в цикле)»?

Не выполняйте оба теста в одной и той же программе (вместо этого перекомпилируйте для каждой версии), или, если вы это сделаете, протестируйте и «версию A первой», и «версию B сначала» и посмотрите, платит ли штраф в зависимости от того, что произойдет раньше.


Иллюстрация обмана:

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

Если timer2 длительность меньше, чем timer1 duration, мы не делаем вывод, что умножение на 31 быстрее, чем умножение на 2. Вместо этого мы понимаем, что компилятор выполнил общий анализ подвыражений, и код стал:

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

И единственное доказанное, что умножение на 31 быстрее, чем вычисление common. Что совсем не удивительно - умножение намного быстрее, чем sqrt и exp.

ответил Ben Voigt 31 Maypm12 2012, 21:58:55

Похожие вопросы

Популярные теги

security × 330linux × 316macos × 2827 × 268performance × 244command-line × 241sql-server × 235joomla-3.x × 222java × 189c++ × 186windows × 180cisco × 168bash × 158c# × 142gmail × 139arduino-uno × 139javascript × 134ssh × 133seo × 132mysql × 132