Подтвердить что ты не робот

X> -1 vs x >= 0, есть ли разница в производительности

Я слышал, как учитель ушел из этого раз, и с тех пор он меня прослушивал. Скажем, мы хотим проверить, больше ли целое число x или равно 0. Существует два способа проверить это:

if (x > -1){
    //do stuff
}

и

if (x >= 0){
    //do stuff
} 

В соответствии с этим учителем > будет немного быстрее, чем >=. В этом случае это была Java, но, по его словам, это также относится к C, С++ и другим языкам. Есть ли правда в этом утверждении?

4b9b3361

Ответ 1

Нет никакой разницы в каком-либо смысле в реальном мире.

Посмотрим на некоторый код, созданный различными компиляторами для разных целей.

  • Я предполагаю подписанную операцию int (которая кажется целью OP)
  • Я ограничил опрос C и компиляторами, что я с готовностью под рукой (правда, довольно маленький образец - GCC, MSVC и IAR).
  • включена базовая оптимизация (-O2 для GCC, /Ox для MSVC, -Oh для IAR)
  • используя следующий модуль:

    void my_puts(char const* s);
    
    void cmp_gt(int x) 
    {
        if (x > -1) {
            my_puts("non-negative");
        }
        else {
            my_puts("negative");
        }
    }
    
    void cmp_gte(int x) 
    {
        if (x >= 0) {
            my_puts("non-negative");
        }
        else {
            my_puts("negative");
        }
    }
    

И вот то, что каждый из них произвел для операций сравнения:

MSVC 11 с таргетингом на ARM:

// if (x > -1) {...
00000        |cmp_gt| PROC
  00000 f1b0 3fff    cmp         r0,#0xFFFFFFFF
  00004 dd05         ble         |[email protected]_gt|


// if (x >= 0) {...
  00024      |cmp_gte| PROC
  00024 2800         cmp         r0,#0
  00026 db05         blt         |[email protected]_gte|

Назначение MSVC 11 x64:

// if (x > -1) {...
cmp_gt  PROC
  00000 83 f9 ff     cmp     ecx, -1
  00003 48 8d 0d 00 00                  // speculative load of argument to my_puts()
    00 00        lea     rcx, OFFSET FLAT:$SG1359
  0000a 7f 07        jg  SHORT [email protected]_gt

// if (x >= 0) {...
cmp_gte PROC
  00000 85 c9        test    ecx, ecx
  00002 48 8d 0d 00 00                  // speculative load of argument to my_puts()
    00 00        lea     rcx, OFFSET FLAT:$SG1367
  00009 79 07        jns     SHORT [email protected]_gte

MSVC 11, ориентированный на x86:

// if (x > -1) {...
_cmp_gt PROC
  00000 83 7c 24 04 ff   cmp     DWORD PTR _x$[esp-4], -1
  00005 7e 0d        jle     SHORT [email protected]_gt


// if (x >= 0) {...
_cmp_gte PROC
  00000 83 7c 24 04 00   cmp     DWORD PTR _x$[esp-4], 0
  00005 7c 0d        jl  SHORT [email protected]_gte

GCC 4.6.1 таргетинг на x64

// if (x > -1) {...
cmp_gt:
    .seh_endprologue
    test    ecx, ecx
    js  .L2

// if (x >= 0) {...
cmp_gte:
    .seh_endprologue
    test    ecx, ecx
    js  .L5

GCC 4.6.1 таргетинг на x86:

// if (x > -1) {...
_cmp_gt:
    mov eax, DWORD PTR [esp+4]
    test    eax, eax
    js  L2

// if (x >= 0) {...
_cmp_gte:
    mov edx, DWORD PTR [esp+4]
    test    edx, edx
    js  L5

GCC 4.4.1 таргетинг на ARM:

// if (x > -1) {...
cmp_gt:
    .fnstart
.LFB0:
    cmp r0, #0
    blt .L8

// if (x >= 0) {...
cmp_gte:
    .fnstart
.LFB1:
    cmp r0, #0
    blt .L2

IAR 5.20 нацеливается на ARM Cortex-M3:

// if (x > -1) {...
cmp_gt:
80B5 PUSH     {R7,LR}
.... LDR.N    R1,??DataTable1  ;; `?<Constant "non-negative">`
0028 CMP      R0,#+0
01D4 BMI.N    ??cmp_gt_0

// if (x >= 0) {...
cmp_gte:
 80B5 PUSH     {R7,LR}
 .... LDR.N    R1,??DataTable1  ;; `?<Constant "non-negative">`
 0028 CMP      R0,#+0
 01D4 BMI.N    ??cmp_gte_0

Если вы все еще со мной, вот разница между записью между оценкой (x > -1) и (x >= 0), которые отображаются:

  • Таргетинг MSVC для ARM использует cmp r0,#0xFFFFFFFF для (x > -1) vs cmp r0,#0 для (x >= 0). Первый код операции инструкции длиннее на два байта. Я полагаю, что это может ввести некоторое дополнительное время, поэтому мы будем называть это преимуществом для (x >= 0)
  • Таргетинг MSVC x86 использует cmp ecx, -1 для (x > -1) vs test ecx, ecx для (x >= 0). Первый код операции команды - один байт дольше. Я полагаю, что это может ввести некоторое дополнительное время, поэтому мы будем называть это преимуществом для (x >= 0)

Обратите внимание, что GCC и IAR генерируют идентичный машинный код для двух видов сравнения (за исключением, возможно, того, какой регистр использовался). Таким образом, согласно этому опросу, кажется, что (x >= 0) имеет очень мало шансов быть "быстрее". Но любое преимущество, которое может иметь минимально короткое кодирование байта кода операции (и, возможно, ударение), безусловно, будет полностью омрачено другими факторами.

Я был бы удивлен, если бы вы нашли что-то другое для jitted-выхода Java или С#. Я сомневаюсь, что вы найдете какую-либо разницу в заметке даже для очень маленькой цели, такой как 8-битный AVR.

Короче говоря, не беспокойтесь об этой микро-оптимизации. Я думаю, что моя запись здесь уже потратила больше времени, чем будет потрачена на любую разницу в производительности этих выражений, накопленных на всех процессорах, выполняющих их в моей жизни. Если у вас есть возможность измерить разницу в производительности, пожалуйста, приложите свои усилия к чему-то более важному, например, изучению поведения субатомных частиц или что-то в этом роде.

Ответ 2

Он очень сильно зависит от базовой архитектуры, но любая разница будет незначительной.

Во всяком случае, я бы ожидал, что (x >= 0) будет немного быстрее, так как сравнение с 0 будет доступно на некоторых наборах инструкций (таких как ARM).

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

Ответ 3

Ваш учитель читает некоторые действительно старые книги. Раньше это имело место с некоторыми архитектурами, в которых отсутствовала команда greater than or equal, для оценки > требовалось меньше машинных циклов, чем >=, но в наши дни эти платформы редки. Я предлагаю перейти на читаемость и использовать >= 0.

Ответ 4

Более серьезная проблема заключается в преждевременной оптимизации. Многие считают, что писать читаемый код важнее, чем писать эффективный код [1, 2]. Я бы применил эти оптимизации как последний этап в библиотеке низкого уровня после того, как проект доказал свою эффективность.

Вам не следует постоянно рассматривать возможность создания мини-оптимизаций в вашем коде за счет удобства чтения, поскольку это упростит чтение и работу с кодом. Если эти оптимизации должны произойти, отвлечь их на функции нижнего уровня, чтобы вы по-прежнему оставались с кодом, который легче читать для людей.

Как сумасшедший пример, подумайте о том, кто пишет свои программы в сборке тем, кто хочет отказаться от этой дополнительной эффективности и использовать Java для своих преимуществ при проектировании, простоте использования и ремонтопригодности.

В качестве побочного примечания, если вы используете C, возможно, написать макрос, который использует немного более эффективный код, является более приемлемым решением, поскольку он обеспечит эффективность, удобочитаемость и удобство обслуживания более чем разбросанные операции.

И, конечно, компромиссы эффективности и удобочитаемости зависят от вашего приложения. Если этот цикл работает 10000 раз в секунду, то это, возможно, узкое место, и вы можете потратить время на его оптимизацию, но если это один оператор, который иногда вызывал это, вероятно, не стоил бы его для минутного выигрыша.

Ответ 5

Да, есть разница, вы должны увидеть байт-код.

для

    if (x >= 0) {
    }

байт-код

    ILOAD 1
    IFLT L1

для

if (x > -1) {
}

байт-код

ILOAD 1
ICONST_M1
IF_ICMPLE L3

версия 1 выполняется быстрее, поскольку используется специальный оператор с нулевым операндом

iflt : jump if less than zero 

Но можно видеть, что разница только работает JVM в режиме только для интерпретации java -Xint ..., например, этот тест

    int n = 0;       
    for (;;) {
        long t0 = System.currentTimeMillis();
        int j = 0;
        for (int i = 100000000; i >= n; i--) {
            j++;
        }
        System.out.println(System.currentTimeMillis() - t0);
    }

показывает 690 мс для n = 0 и 760 мс для n = 1. (я использовал 1 вместо -1, потому что его легче продемонстрировать, идея остается той же)

Ответ 6

На самом деле, я считаю, что вторая версия должна быть немного быстрее, так как для нее требуется проверка на один бит (при условии, что вы сравниваете на ноль, как показано выше). Однако такая оптимизация никогда не проявляется, поскольку большинство компиляторов оптимизирует такие вызовы.

Ответ 7

" > =" - единственная операция, как и " > ". Не два отдельных операции с OR.

Но >= 0, вероятно, быстрее, потому что компьютер должен проверять только один бит (отрицательный знак).

Ответ 8

В соответствии с этим учителем > будет немного быстрее, чем > =. В этом Дело было в Java, но, по его словам, это также применялось для C, С++ и другие языки. Есть ли правда в этом утверждении?

Ваш учитель принципиально ошибается. Не только то, почему вероятность, чем сравнение с 0, может быть очень быстро, но потому, что такая локальная оптимизация хорошо сделана вашим компилятором/интерпретатором, и вы можете испортить все попытки помочь. Определенно не очень хорошо учиться.

Вы можете прочитать: this или this

Ответ 9

Извините, что вступил в разговор о производительности.

Прежде чем я отвлекся, учтите, что JVM имеет специальные инструкции для обработки не только нуля, но и констант с одного по три. С учетом сказанного, вероятно, что способность архитектуры обрабатывать ноль уже давно потеряна по сравнению с оптимизацией компилятора, а также байт-код для машинного перевода кода и т.д.

Я помню из моих дней ассемблера x86, что в наборе для команд были больше, чем (ja) и больше или равно (jae). Вы бы сделали одно из следующих действий:

; x >= 0
mov ax, [x]
mov bx, 0
cmp ax, bx
jae above

; x > -1
mov ax, [x]
mov bx, -1
cmp ax, bx
ja  above

Эти альтернативы занимают одинаковое количество времени, потому что инструкции идентичны или похожи, и они потребляют предсказуемое количество тактовых циклов. См., Например, this. ja и jae могут действительно проверять различное количество арифметических регистров, но в этой проверке преобладает необходимость того, чтобы инструкция выполняла предсказуемое время. Это, в свою очередь, необходимо для сохранения архитектуры процессора.

Но я пришел сюда, чтобы отвлечься.

Ответы передо мной имеют тенденцию быть уместными, а также указывают на то, что вы будете в одном и том же шаром в той мере, в какой это касается производительности, независимо от того, какой подход вы выберете.

Который оставляет вас с выбором по другим критериям. И здесь я хотел сделать заметку. При тестировании индексов предпочитайте жесткую проверку стиля привязки, главным образом x >= lowerBound, до x > lowerBound - 1. Аргумент должен быть изобретен, но он сводится к удобочитаемости, так как здесь все остальное действительно равно.

Поскольку концептуально вы тестируете нижнюю границу, x >= lowerBound - это канонический тест, который вызывает наиболее адаптированное познание от читателей вашего кода. x + 10 > lowerBound + 9, x - lowerBound >= 0 и x > -1 - все окольные пути для проверки нижней границы.

Снова, извините, что вступил, но я чувствовал, что это важно для ученых. Я всегда думаю в этих терминах и позволяю компилятору беспокоиться о мельчайших оптимизациях, которые, по его мнению, могут возникнуть из-за постоянных констант и строгости операторов.

Ответ 10

Прежде всего, это зависит от аппаратной платформы. Для современных ПК и ARM SoCs разница в основном зависит от оптимизации компилятора. Но для процессоров без FPU подписанная математика будет катастрофой.

Например, простые 8-битные ЦП, такие как Intel 8008, 8048,8051, Zilog Z80, Motorola 6800 или даже современные микроконтроллеры RISC PIC или Atmel, выполняют всю математику через ALU с 8-битными регистрами и имеют в основном только бит флага и z (бит значения нуля). Вся серьезная математика выполняется через библиотеки, а выражение

  BYTE x;
  if (x >= 0) 

определенно выиграет, используя команды JZ или JNZ asm без дорогостоящих вызовов библиотеки.