Skip to content

Оптимизация компилятора для преобразований между строками и байтовыми срезами

Ранее упоминалось, что базовые байты при преобразованиях между строками и байтовыми слайсами будут скопированы. Стандартный компилятор Go делает некоторые оптимизации, которые, как доказано, все еще работают в Go Toolchain 1.19, для некоторых особых сценариев, чтобы избежать дублирования копий. Эти сценарии включают в себя:

  • Преобразование из строки в байтовый фрагмент, которое следует за ключевым словом range в for-range цикле.

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

  • Преобразование из байтового слайса в строку, используемое при сравнении.

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

Пример:

Самое время протестировать код!

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

Другой пример:

For-range на строках

Поток управления циклом for-range применяется к строкам. Но обрати внимание, for-range циклы будут повторять кодовые точки Unicode как значения rune вместо байтов в строке. Неправильные представления кодировки UTF-8 в строке будут интерпретироваться как тип rune со значением 0xFFFD.

Пример:

Самое время протестировать код!

Из выходного результата мы можем понять, что

  1. Значение индекса итерации не всегда является непрерывным. Причина в том, что индекс — это индекс байта в ранжированной строке, и для представления одной кодовой точки может потребоваться более одного байта.

  2. Первый символ é состоит из двух рун (всего 3 байта).

  3. Второй символ क्षि состоит из четырех рун (всего 12 байт).

  4. Английский символ a, состоит из одной руны (1 байт).

  5. Символ π,  состоит из одной руны (2 байта).

  6. Китайский иероглиф 囧,  состоит из одной руны (3 байта).

Тогда как перебирать байты в строке? Пример:

Самое время протестировать код!

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

Самое время протестировать код!

Из приведенных выше нескольких примеров мы знаем, что len(s) вернет количество байтов в строке s. Временная сложность len(s) будет O(1). Как получить количество рун в строке? Нужно использовать for-range цикл для перебора и подсчета всех рун — это один из способов. Второй способ —использование функции RuneCountInString в стандартном пакете unicode/utf8.

Подробнее о  функции RuneCountInString ты можешь узнать по ссылке.

Внимание! Метод не всегда верно считает символы для emoji, например, для флагов стран. Перед использованием убедись, что ты работаешь с текстовыми данными, входящими в суперсет utf8.

Самое время протестировать код!

Эффективность обоих способов почти одинакова. Третий способ — len([]rune(s)) — позволяет получить количество рун в строке s. Начиная с версии Go Toolchain 1.11, стандартный компилятор Go оптимизирует третий способ, чтобы избежать ненужного глубокого копирования, тогда этот способ становится таким же эффективным, как первые два. Обрати внимание, что временные сложности этих способов все O(n).

Рекомендуем ознакомиться со следующим материалом: