х> -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)
vscmp r0,#0
на(x >= 0)
. Код операции первой инструкции на два байта длиннее. Я полагаю, что это может ввести некоторое дополнительное время, поэтому мы назовем это преимуществом для(x >= 0)
- индекса MSVC ориентации на x86 использует
cmp ecx, -1
на(x > -1)
vstest 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 без очень дорогостоящих вызовов библиотеки.