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

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

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

и

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

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

10 ответов


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

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

  • я предполагаю, что подписанная операция 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         |$LN2@cmp_gt|


// if (x >= 0) {...
  00024      |cmp_gte| PROC
  00024 2800         cmp         r0,#0
  00026 db05         blt         |$LN2@cmp_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 $LN5@cmp_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 $LN5@cmp_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 $LN2@cmp_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 $LN2@cmp_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 таргетинг Рука:

// 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 или C#. Я сомневаюсь, что вы найдете какую-либо разницу даже для очень маленькой цели, такой как 8-битный AVR.

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


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

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

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


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


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

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

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

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

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


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

на

    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, потому что это легче продемонстрировать, идея остается той же)


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


">= " одиночная деятельность, как раз как ">". Не 2 отдельные операции или.

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


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

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

вы можете прочитать: этой или этой


Извините, что вмешиваюсь в этот разговор о производительности.

прежде чем я отвлекусь, отметим, что у 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

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

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

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

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

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


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

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

  BYTE x;
  if (x >= 0) 

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