Strcmp для пустой строки
Я просматривал код, и я видел, как кто-то сделал
if (0 == strcmp(foo,""))
Мне любопытно, потому что я думаю, что быстрее будет делать
if (foo[0] == '\0')
Правильно ли это, или strcmp оптимизирован, чтобы сделать их одинаковыми.
(Я понимаю, что даже если бы была какая-то разница, она была бы небольшой, но я думаю, что вы сохраните хотя бы несколько инструкций, используя мой метод.)
Ответы
Ответ 1
Вы правы: поскольку вызов strcmp()
добавляет управление стеком и переход к фактическим инструкциям strcmp, вы получите несколько инструкций, просто проверив первый байт вашей строки.
Для вашего любопытства вы можете проверить код strcmp() здесь: http://sourceware.org/git/?p=glibc.git;a=blob;f=string/strcmp.c;h=bd53c05c6e21130b091bd75c3fc93872dd71fe4b;hb=HEAD
(Я думал, что код будет заполнен #ifdef
и неясным __GNUSOMETHING
, но он на самом деле довольно простой!)
Ответ 2
strcmp() является вызовом функции и, следовательно, имеет служебную нагрузку функции. foo [0] - прямой доступ к массиву, поэтому он явно быстрее.
Ответ 3
В этом случае я не вижу преимущества использования strcmp. Компилятор мой достаточно умный, чтобы оптимизировать его, но он не будет быстрее, чем проверка "\ 0" байта напрямую. Разработчик этого, возможно, выбрал эту конструкцию, потому что считал ее более читаемой, но я думаю, что это дело вкуса в этом случае. Хотя я бы написал чек немного иначе, поскольку это идиома, которая, как представляется, используется чаще всего для проверки пустой строки:
if( !*str )
и
if( *str )
чтобы проверить непустую строку.
Ответ 4
+1 для Gui13 для предоставления ссылки на источник gcc stdlib strcmp (http://sourceware.org/ git/?p=glibc.git;a=blob;f=string/strcmp.c; ч = bd53c05c6e21130b091bd75c3fc93872dd71fe4b;!= ро ГОЛОВА)
Вы правы, что strcmp никогда не может быть быстрее, чем прямое сравнение [1], но вопрос в том, будет ли компилятор его оптимизировать? Я был испуган, чтобы попытаться измерить это, но я был приятно удивлен, насколько это было легко. Мой пример кода (опуская заголовки):
bool isEmpty(char * str) {
return 0==std::strcmp(str,"");
}
bool isEmpty2(char * str) {
return str[0]==0;
}
И я попытался скомпилировать это, сначала с gcc -S -o- emptystrcmptest.cc
, а затем с помощью gcc -S -O2 -o- emptystrcmptest.cc
. К моему приятному удивлению, хотя я не могу хорошо прочитать сборку, неоптимизированная версия явно показала разницу, и оптимизированная версия четко показала, что две функции выполняют идентичную сборку.
Итак, я бы сказал, что в целом не стоит беспокоиться об этом уровне оптимизации.
Если вы используете компилятор для встроенной системы и знаете, что он не обрабатывает такую простую оптимизацию (или вообще не имеет стандартной библиотеки), используйте специальную версию с особым корпусом.
Если вы обычно кодируете, используйте более читаемую версию (imho, которая может быть strcmp или strlen или [0] == 0 в зависимости от контекста).
Если вы пишете высокоэффективный код, вы ожидаете, что его вызовут тысячи или миллионы раз в секунду, (а) тест, который на самом деле более эффективен, и (б) если читаемая версия на самом деле слишком медленная, попробуйте написать somethign, будет скомпилирован для лучшей сборки.
С gcc -S -o- emptystrcmptest.cc
:
.file "emptystrcmptest.cc"
.section .rdata,"dr"
LC0:
.ascii "\0"
.text
.align 2
.globl __Z7isEmptyPc
.def __Z7isEmptyPc; .scl 2; .type 32; .endef
__Z7isEmptyPc:
pushl %ebp
movl %esp, %ebp
subl $24, %esp
movl $LC0, 4(%esp)
movl 8(%ebp), %eax
movl %eax, (%esp)
call _strcmp
movl %eax, -4(%ebp)
cmpl $0, -4(%ebp)
sete %al
movzbl %al, %eax
movl %eax, -4(%ebp)
movl -4(%ebp), %eax
leave
ret
.align 2
.globl __Z8isEmpty2Pc
.def __Z8isEmpty2Pc; .scl 2; .type 32; .endef
__Z8isEmpty2Pc:
pushl %ebp
movl %esp, %ebp
movl 8(%ebp), %eax
cmpb $0, (%eax)
sete %al
movzbl %al, %eax
popl %ebp
ret
emptystrcmptest.cc:10:2: warning: no newline at end of file
.def _strcmp; .scl 2; .type 32; .endef
С gcc -S -O2 -o- emptystrcmptest.cc
:
.file "emptystrcmptest.cc"
emptystrcmptest.cc:10:2: warning: no newline at end of file
.text
.align 2
.p2align 4,,15
.globl __Z7isEmptyPc
.def __Z7isEmptyPc; .scl 2; .type 32; .endef
__Z7isEmptyPc:
pushl %ebp
movl %esp, %ebp
movl 8(%ebp), %eax
popl %ebp
cmpb $0, (%eax)
sete %al
movzbl %al, %eax
ret
.align 2
.p2align 4,,15
.globl __Z8isEmpty2Pc
.def __Z8isEmpty2Pc; .scl 2; .type 32; .endef
__Z8isEmpty2Pc:
pushl %ebp
movl %esp, %ebp
movl 8(%ebp), %eax
popl %ebp
cmpb $0, (%eax)
sete %al
movzbl %al, %eax
ret
[1] Хотя будьте осторожны - в случаях, когда более сложный, чем прямой тест против нуля, библиотека и код компилятора обычно будут лучше, чем ручной код.
Ответ 5
Хороший оптимизирующий компилятор может оптимизировать вызов функции, а затем исключить цикл из встроенной функции. Нет никакого способа, чтобы ваш метод мог быть медленнее, хотя есть вероятность, что он будет той же скоростью.
Ответ 6
Доступ к массиву - это порядок 1 во время выполнения, поэтому он быстрее, чем функция.
Ответ 7
Это как микрооптимизация по мере ее получения, но я полагаю, что если вы добавили нулевую проверку перед индексом foo (если вы не знаете, что она никогда не будет равна нулю), она технически сохранит накладные расходы на вызов функции.
Ответ 8
Очевидно, что это будет быстрее, и, вероятно, стоит поместить свой собственный код в встроенную функцию или, возможно, даже макрос, если вы планируете двигаться вперед с ним:
int isEmpty(const char *string)
{
return ! *string;
}
int isNotEmpty(const char *string)
{
return *string;
}
int isNullOrEmpty(const char *string)
{
return string == NULL || ! *string;
}
int isNotNullOrEmpty(const char *string)
{
return string != NULL && *string;
}
и пусть компилятор оптимизирует это для вас. Несмотря на это, strcmp
должен в конечном итоге проверить '\0'
, чтобы вы всегда были по крайней мере равны ему. (честно говоря, я бы, вероятно, позволил компилятору оптимизировать внутренний общий доступ выше, например, isEmpty
, возможно, просто перевернул isNotEmpty
)