Однажды я слышал, как учитель бросил это, и с тех пор меня это беспокоит. Допустим, мы хотим проверить, является ли целое число x
больше или равно 0. Есть два способа проверить это:
if (x > -1){
//do stuff
}
а также
if (x >= 0){
//do stuff
}
По словам этого учителя >
будет немного быстрее, чем >=
, В данном случае это была Java, но, по его словам, это также относится к C, c ++ и другим языкам. Есть ли правда в этом утверждении?
Там нет никакой разницы в любом смысле в реальном мире.
Давайте посмотрим на некоторый код, сгенерированный различными компиляторами для различных целей.
-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 для 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)
которые появляются:
cmp r0,#0xFFFFFFFF
за (x > -1)
против cmp r0,#0
за (x >= 0)
, Код операции первой инструкции на два байта длиннее. Я предполагаю, что это может ввести некоторое дополнительное время, поэтому мы назовем это преимуществом для (x >= 0)
cmp ecx, -1
за (x > -1)
против test ecx, ecx
за (x >= 0)
, Код операции первой инструкции длиннее на один байт. Я предполагаю, что это может ввести некоторое дополнительное время, поэтому мы назовем это преимуществом для (x >= 0)
Обратите внимание, что GCC и IAR сгенерировали идентичный машинный код для двух видов сравнения (с возможным исключением того, какой регистр использовался). Итак, согласно этому опросу, кажется, что (x >= 0)
имеет очень небольшой шанс быть «быстрее». Но какое бы преимущество ни имела минимально короткое байтовое кодирование кода операции (и я подчеркиваю, должно быть) наверняка будут полностью омрачены другими факторами.
Я был бы удивлен, если бы вы нашли что-то другое для сопряженного вывода Java или C #. Я сомневаюсь, что вы найдете какую-либо разницу в примечаниях даже для очень маленькой цели, такой как 8-битный AVR.
Короче, не беспокойтесь об этой микрооптимизации. Я думаю, что моя запись здесь уже потратила больше времени, чем будет потрачено на любую разницу в производительности этих выражений, накопленную на всех процессорах, выполняющих их в моей жизни. Если у вас есть возможность измерить разницу в производительности, пожалуйста, приложите свои усилия к чему-то более важному, например, изучите поведение субатомных частиц или что-то еще.
Это очень сильно зависит от базовой архитектуры, но любая разница будет незначительной.
Во всяком случае, я бы ожидал (x >= 0)
быть немного быстрее, по сравнению с 0
поставляется бесплатно на некоторых наборах инструкций (таких как ARM).
Конечно, любой разумный компилятор выберет лучшую реализацию независимо от того, какой вариант находится в вашем источнике.
Ваш учитель читал несколько действительно старых книг. Раньше такое случалось с некоторыми архитектурами, не имеющими greater than or equal
инструкция, которая оценивает >
требуется меньше машинных циклов, чем >=
Но эти платформы редки в наши дни. Я предлагаю перейти на удобочитаемость и использовать >= 0
,
Большая проблема здесь преждевременная оптимизация. Многие считают, что писать удобочитаемый код важнее написания эффективное код [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 отдельные операции с OR.
Но> = 0, вероятно, быстрее, потому что компьютеру нужно проверять только один бит (отрицательный знак).
Согласно этому учителю> будет немного быстрее, чем> =. В этом
в случае, если это была Java, но, по его словам, это также относится и к C, C ++
и другие языки. Есть ли правда в этом утверждении?
Ваш учитель в корне не прав.
Не только почему шанс, чем сравнение с 0, может быть довольно быстрым, но и потому, что этот вид локальной оптимизации хорошо сделан вашим компилятором / интерпретатором, и вы можете испортить все попытки помочь. Определенно, нехорошо учить.