Почему использование isEmpty предпочтительнее, чем сравнение с пустым строковым литералом Swift?

Тип String - Swift имеет свойство с именем isEmpty это указывает, нет ли в строке символов.

Я хотел бы знать, есть ли разница между использованием isEmptyи проверка равенства пустому строковому литералу. Другими словами, этоmyString.isEmpty лучше чем myString == ""?

Я провел небольшое исследование и нашел следующие рекомендации:

  1. Ссылка на строковую структуру в документации для разработчиков Apple (а также в Swift Language Guide) рекомендует использоватьisEmpty вместо проверки длины строки:

Чтобы проверить, пуста ли строка, используйте ее isEmpty свойство вместо сравнения длины одного из представлений с 0. В отличие от isEmpty, вычисление свойства count для представления требует перебора элементов строки.

  1. В ответе Роба Нэпьера на несколько иной вопрос 2015 года на Stackru говорится следующее:

    Пустая строка - единственная пустая строка, поэтому не должно быть случаев, когда string.isEmpty() не возвращает то же значение, что и string == "". Конечно, они могут делать это в разное время и в разной памяти. Используют ли они разное количество времени и памяти - это не описанные детали реализации, ноisEmpty является предпочтительным способом проверки в Swift (как указано в документации).

  2. Некоторые сообщения в блогах также рекомендуют использовать isEmpty особенно вместо того, чтобы проверять, равна ли длина строки 0.

Однако ни один из этих источников не говорит ничего против сравнения с пустым литералом.

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

Тем не менее, мне любопытно, плохо ли сравнение с пустым строковым литералом. Действительно ли это влияет на память или производительность? Возможно, компилятор Swift в наши дни настолько умен, что будет производить тот же двоичный код дляmyString.isEmpty а также myString == ""? Я чувствую, что разница должна быть незначительной или даже отсутствовать, но у меня нет доказательств.

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

1 ответ

В качестве примечания, isEmpty является предпочтительным / рекомендуемым методом проверки пустоты коллекции, так как все Collection типы гарантируют, что isEmptyвозвращается в O(1) (или, по крайней мере, это справедливо для коллекций стандартной библиотеки). Оператор равенства не дает таких гарантий, поэтому, если вас интересует только коллекция, имеющая или не имеющая элементов (например, для запуска операции обработки), тогда isEmpty это определенно путь.

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

func testEmpty(_ str: String) -> Bool { str.isEmpty }

приводит к следующему ассемблерному коду:

                     _$s3CL29testEmptyySbSSF:
0000000100002c70         push       rbp
0000000100002c71         mov        rbp, rsp
0000000100002c74         mov        rax, rsi
0000000100002c77         shr        rax, 0x38
0000000100002c7b         and        eax, 0xf
0000000100002c7e         movabs     rcx, 0xffffffffffff
0000000100002c88         and        rcx, rdi
0000000100002c8b         bt         rsi, 0x3d
0000000100002c90         cmovae     rax, rcx
0000000100002c94         test       rax, rax
0000000100002c97         sete       al
0000000100002c9a         pop        rbp
0000000100002c9b         ret        

в то время как

func testEqual(_ str: String) -> Bool { str == "" }

генерирует это:

                     _$s3CL29testEqualySbSSF:
0000000100002cd0         push       rbp
0000000100002cd1         mov        rbp, rsp
0000000100002cd4         movabs     rcx, 0xe000000000000000
0000000100002cde         test       rdi, rdi
0000000100002ce1         jne        0x100002cec

0000000100002ce3         cmp        rsi, rcx
0000000100002ce6         jne        0x100002cec

0000000100002ce8         mov        al, 0x1
0000000100002cea         pop        rbp
0000000100002ceb         ret        

0000000100002cec         xor        edx, edx                                    ; XREF=_$s3CL29testEqualySbSSF+17, _$s3CL29testEqualySbSSF+22
0000000100002cee         xor        r8d, r8d
0000000100002cf1         pop        rbp
0000000100002cf2         jmp        imp___stubs__$ss27_stringCompareWithSmolCheck__9expectingSbs11_StringGutsV_ADs01_G16ComparisonResultOtF
                        ; endp

Обе сборки создаются в режиме Release со всеми включенными оптимизациями. Кажется, что для isEmpty вызов компилятора может использовать некоторые ярлыки, поскольку он знает о внутреннем String структура.

Но мы можем убрать это, сделав наши функции универсальными:

func testEmpty<S: StringProtocol>(_ str: S) -> Bool { str.isEmpty }

производит

                     _$s3CL29testEmptyySbxSyRzlF:
0000000100002bd0         push       rbp
0000000100002bd1         mov        rbp, rsp
0000000100002bd4         push       r13
0000000100002bd6         push       rax
0000000100002bd7         mov        rax, rsi
0000000100002bda         mov        rcx, qword [ds:rdx+8]
0000000100002bde         mov        rsi, qword [ds:rcx+8]
0000000100002be2         mov        r13, rdi
0000000100002be5         mov        rdi, rax
0000000100002be8         call       imp___stubs__$sSl7isEmptySbvgTj
0000000100002bed         add        rsp, 0x8
0000000100002bf1         pop        r13
0000000100002bf3         pop        rbp
0000000100002bf4         ret        
                        ; endp

, в то время как

func testEqual<S: StringProtocol>(_ str: S) -> Bool { str == "" }

производит

                     _$s3CL29testEqualySbxSyRzlF:
0000000100002c00         push       rbp
0000000100002c01         mov        rbp, rsp
0000000100002c04         push       r14
0000000100002c06         push       r13
0000000100002c08         push       rbx
0000000100002c09         sub        rsp, 0x18
0000000100002c0d         mov        r14, rdx
0000000100002c10         mov        r13, rsi
0000000100002c13         mov        rbx, rdi
0000000100002c16         mov        qword [ss:rbp+var_28], 0x0
0000000100002c1e         movabs     rax, 0xe000000000000000
0000000100002c28         mov        qword [ss:rbp+var_20], rax
0000000100002c2c         call       _$sS2SSysWl
0000000100002c31         mov        rcx, qword [ds:imp___got__$sSSN]
0000000100002c38         lea        rsi, qword [ss:rbp+var_28]
0000000100002c3c         mov        rdi, rbx
0000000100002c3f         mov        rdx, r13
0000000100002c42         mov        r8, r14
0000000100002c45         mov        r9, rax
0000000100002c48         call       imp___stubs__$sSysE2eeoiySbx_qd__tSyRd__lFZ
0000000100002c4d         add        rsp, 0x18
0000000100002c51         pop        rbx
0000000100002c52         pop        r13
0000000100002c54         pop        r14
0000000100002c56         pop        rbp
0000000100002c57         ret        
                        ; endp

Аналогичные результаты, isEmpty код результатов меньше кода сборки, что делает его быстрее.

Никакой разницы в производительности (по сравнению с == ""). Так просто удобнее.

Другие вопросы по тегам