интерпретаторе • Знаю о сторонних библиотеках (что можно улучшить в str) • Приколы о Юникоде, внутреннее представление str • Если внимательно читать доки… Почему не другие типы данных? • dict уже хорош (и нет идей): https://habr.com/ru/articles/432996/ • dict стал ordered в 3.6 (обнаружили и описали это в доках в 3.7) • set стал ordered в 3.11
графем!) есть номер или codepoint • Диапазон: 0x0000 – 0x10FFFF (чаще пишут U+0000 – U+10FFFF) • Технически возможно 2^20+2^16−2^11 (1 112 064) символов как в UTF-16
Set (UCS): UCS-1, UCS-2 и UCS-4 (синхр. с ISO/IEC 10646) • Семейство кодировок: UTF-8, UTF-16 (BE и LE), UTF-32 (BE и LE) • UCS-4 == UTF-32 • UCS-2 ⸦ UTF-16, ибо каждая UTF-кодировка может покрыть весь UCS-4 • UCS-1 == ASCII ⸦ UTF-8 • UTF-7 не вошла в стандарт; есть первоапрельские UTF-9 и UTF-18 • Иногда говорят Юникод, подразумевают UTF-8 (но это не всегда так!)
символ) • 1992: начата синхронизация с ISO/IEC 10646 • 1996: Unicode 2.0 (38 950 символов) технически мог > 2^16 • 2001: Unicode 3.1 (94 205 символов), появились первые символы > 2^16 • 2008: по данным Google UTF-8 стала самой популярной кодировкой (с выходом Unicode 5.1) • 2024: Unicode 16.0 (155 063 символа), UTF-8 используют 98,3% веб-сайтов
UCS-4 символы представляются т.н. «суррогатной парой» (surrogate pair) или 2 code unit’а UTF-16 символы: • U+0000 – U+D7FF (простые символы, UCS-2) • U+D800 – U+DBFF (первые половинки суррогатных пар – high surrogate) • U+DC00 – U+DFFF (вторые половинки суррогатных пар – low surrogate) • U+E000 – U+FFFF («частное использование» / Private-Use) – никогда не будут определены, но можно договориться и юзать в своих приложениях
пар) • U+DC00 – U+DFFF (вторые половинки суррогатных пар) • Некорректную строку создать можно! Похоже на баг. • И вообще, для str нет гарантии корректности!
длины • UCS-2 и UCS-4 символы представляются с помощью т.н. continuation bytes + финальный UTF-8 символы: • U+0000 – U+007F (== ASCII == UCS-1): длина в codepoint’ах == длина в байтах • Все остальные (2, 3 или 4 байта)
3.2: в метод .splitlines() добавлены разделители \v и \f • 3.7: метод .format() перестал быть thread safe! • из-за вызова setlocale() для float • 3.7: добавлен .isascii() • 3.9: добавлены .removeprefix() и .removesuffix() • 3.12: сняты большинство ограничений для f-строк • 3.13: метод .format() снова thread safe в no-GIL версии • были незначительные улучшения в форматировании
виде строк): • string.ascii_letters • string.digits • string.whitespace • Функция string.capwords() полезнее str.title(): • Нет аналога str.istitle()! Идея уровня good first issue! • Классы string.Template и string.Formatter
LoC (lines of code) • Структура PyUnicodeObject и методы + атрибуты str • для всех трёх представлений • Модуль _string целиком там (он небольшой, но можно вынести) • Два разных итератора для str:
LoC (lines of code) • Структура PyUnicodeObject и методы + атрибуты str • для всех трёх представлений • Модуль _string целиком там (он небольшой, но можно вынести) • Два разных итератора для str: • Файл ./Objects/clinic/unicodeobject.c.h – почти 2k LoC • Там сгенерированные обёртки, обрабатывающие *args и **kwargs для методов • и вызовы unicode_rsplit_impl(self, sep, maxsplit); из unicodeobject.c
метки! Например: • Диакритические знаки (умляуты, точки, крышки, перечеркивания (ять) и т.п.) • Ударения (несколько видов, в русском есть только «острое ударение») • … • Они меняют написание предыдущего символа! И имеют свой codepoint!
mortal • Смотри ./InternalDocs/string_interning.md • (1) Immortal static interned strings – статически аллоцированы (на стеке): • все ключевые слова, пустая строка, • все 1-символьные строки из “latin-1” (U+00 – U+FF) • имена для builtins переменных, функций и классов (например, исключений), … • Часть в ./Include/internal/pycore_unicodeobject_generated.h • В ./Objects/unicodeobject.c хранятся в словаре, но он их не чистит
динамически аллоцированы (malloc), free в конце • Могут быть shared между sub-interpreters (в т.ч. унаследованы от main interp.) • Sub-interpreters стали публичными в 3.14 • https://habr.com/ru/articles/928054/ «PEP-734: Субинтерпретаторы в Python 3.14» (Космотекст?)
аллоцированы, и могут быть освобождены • например, константы в байт-коде • некоторые встроенные атрибуты у модулей и т.п. • Вызовы: _PyUnicode_InternMortal( и PyUnicode_InternInPlace( • Если хотите строку «интернировать» через C API: • not interned --> mortal --> immortal • Переход в mortal требует взять GIL, затем отпустить (на начало мая 2025)
в чистую C библиотеку функций (без PyObject*) • Частично есть: ./Objects/stringlib/ (и нужны не все методы: 1-3) • find.h, join.h, … • asciilib.h, ucs1lib.h, ucs2lib.h, ucs4lib.h • Самое сложное: кодеки для # encoding: <кодировка>
в чистую C библиотеку функций (без PyObject*) • Частично есть: ./Objects/stringlib/ (и нужны не все методы: 1-3) • find.h, join.h, … • asciilib.h, ucs1lib.h, ucs2lib.h, ucs4lib.h • В ./Objects/stringlib/codecs.h – только UTF-8, 16, 32 кодеки на C • Built-in кодеки – прямо в unicodeobject.c (~ 4,5 тыс. строк: ~3829-8385) • Обвязка для встроенных кодеков: ./Modules/_codecsmodule.c • Обвязка на C для кодеков азиатских (CJK) языков: ./Modules/cjkcodecs/ • Реестр кодеков: ./Python/codecs.c (можно регать свой на чистом Python)
(base64 и т.п.) – в AST парсер их нельзя • Большинство кодировок – в пакете на чистом Python: ./Lib/encodings/ • всего: около 120 кодировок (почти все – текстовые) • 65 из них – авто-генерированные через ./Tools/unicode/gencodec.py • 2-4 коммита в год • последний раз новый кодек добавлялся в 2016 • => сконвертировать скриптом в Cишный код – технически несложно
найти реальные применения custom кодеков • Есть скрипты от Victor Stinner для поиска по пакетам на PyPI • https://hugovk.dev/blog/2022/how-to-search-5000-python-projects/ • Спасибо Михаилу Ефимову за ссылку выше • Tsche/magic_codec (35 звёзд) • pydong.org/posts/PythonsPreprocessor/ • Описать идею сначала на Discuss форуме или issue в python/cpython • Допустимо ли ограничить AST parser только Сишными кодировками? • Если нет, возможно ли иметь C-only реализацию парсера + его расширение с загрузкой CPython рантайма и добавлением Python-only кодеков? • … • При одобрении детально оформить PEP в виде PR в python/peps
памяти хранит строки в UTF-8 , исходные файлы чаще в UTF-8 • Компилятор сохраняет байткод в .pyc файл строго в UTF-8 • Байткод в памяти (PyCodeObject*) хранит str в адаптивном представлении • А может str внутри переделать на UTF-8?
памяти хранит строки в UTF-8 , исходные файлы чаще в UTF-8 • Компилятор сохраняет байткод в .pyc файл строго в UTF-8 • Байткод в памяти (PyCodeObject*) хранит str в адаптивном представлении • А может str внутри переделать на UTF-8? • faster-cpython/ideas/issues/684 Use UTF-8 internally for strings. (Mark Shannon) • Есть даже прототип Cишной структуры • Но активности пока мало • Может зацепить регулярные выражения (re и подмодули _sre на чистом C)
веб-сайтов, AST и байткода • Компактность для смешанных строк (latin-1 + не latin-1) Против UTF-8: • Индексация – O(N) для UTF-8 вместо O(1) для адаптивного представления • нужна примерно в половине методов • Строки на CJK-языках в 1.5 раза больше, чем в UCS-2 Нейтрально: • O(N) при создании останется: вместо maxchar -> взять длину в codepoint’ах
последовательная • SIMD – Single Instruction Multiple Data (векторизация? её нет!) • AVX-2, AVX-512 в x86 • SSE3, SSE4 в ARM • Гибкая векторизация в RISC-V
ли внутри) • [+/-] векторизация (CPU SIMD extensions): x86 (есть AVX-512), ARM • [+] есть Python bindings (Rust, Swift и т.д.), половина методов + доп. методы • simdutf: simdutf/simdutf – 1.4k звёзд • [+] векторизация (CPU SIMD extensions): x86, ARM, RISC-V • [-] нет Python bindings вообще • [+/-] UTF-8 и transcoding в/из UTF-16 и UTF-32, отдельно “base64” (не для строк) • [+] есть модульность (generic / vectorized реализации отделены) • В «проде» у многих JS-related проектов: Chromium, Webkit, Bun (JS JIT), … • [-] обе внутри на C++ • Лицензии: Apache 2.0 / BSD 3-clause и Apache 2.0 / MIT
Написана на чистом Mojo (самая читабельная) • Есть векторизация (встроена в язык) • По неидентифицированным бенчмаркам быстрее fastvalidate-utf8 (13.16 GB/s) • Можно сравнить с другими только на Linux (где Mojo доступен)
(нет сравнений) • по функциональности (StringZilla должна победить, в 4.0 будет быстрый hashing!) • performance на тех же бенчмарках (сделать общие хотя бы микро-бенчмарки) • по совместимости с набором платформ (simdutf должен победить) • В наборе pyperformance не видно фокуса на строках • Нужны real world бенчмарки: • Есть HuggingFace tokenizers (для ML / DS очень хайповая вещь) • Микробенчмарки: • Датасет lemire/unicode_lipsum • Датасет wikipedia_mars (есть в simdutf)
performance тестов нужно: • Выровнять частоту ядер (выкл. турбо буст и т.п.) • Выделить пару ядер под систему (на Linux), остальные – под user процессы • У NVIDIA есть хорошие гайды по настройке CPU и I/O: • Rivermax Windows Performance Tuning Guide • Rivermax Linux Performance Tuning Guide
В Python – комбинация Boyer-Moore (BM) и Boyer-Moore-Horspool (BMH) • Разбор алгоритмов с кодом • Либо вызов из стандартной библиотеки C memmem • Сравнение реализаций memmem (by Simone Faro, март 2025): • https://rurban.github.io/smart/results/best20/englishTexts.html • Мерили через rurban/smart (Reini Urban, автор safeclib) • Самый быстрый алгоритм на большинстве размеров: • EPSM - SSE4 Exact Packed String Matching
строк (PEP) • Перенос кодировок в чистый C (PEP) • Отделение libast.so от libpython.so (PEP, т.к. затронет C API) • Объединение символа с графемой по смыслу (?) • Функция string.iscapwords() • Make .format() thread safe w/GIL! • Адаптировать DTOA из Swift или реализовать алгоритм Ryu • Векторизация CSV, см. C# пакет Sep • “base64” codec из simdutf