В парсере статусов одинаковые байтовые значения преобразуются в строки. Объясните, почему проверка идентичности может дать True и когда такой подход уменьшит память.
import sys
def parse_status(raw):
return sys.intern(raw.decode("ascii"))
raw_values = [b"ok", b"error", b"ok", b"ok"]
states = [parse_status(value) for value in raw_values]
print(states[0] is states[2])
sys.intern() канонизирует одинаковые неизменяемые строки: для одного содержимого он возвращает общий объект. Поэтому states[0] is states[2] обычно напечатает True. Память уменьшается, когда в приложении много повторяющихся строк из небольшого словаря значений; для большого числа уникальных строк интернирование может увеличить расходы и время обработки.
Интернирование появилось как оптимизация для повторно используемых неизменяемых значений, прежде всего строк. Если одинаковые строки представлены одним объектом, не нужно хранить отдельную копию содержимого для каждого вхождения, а сравнение идентичности может быстро подтвердить равенство.
Python может автоматически интернировать некоторые строки, но это деталь реализации и на неё нельзя опираться в прикладной логике. Явное использование sys.intern() делает намерение разработчика очевидным, но требует подходящего набора данных.
При разборе миллионов записей часто повторяются статусы, имена полей, коды регионов или команды. Без интернирования декодирование каждого значения может создавать отдельные строковые объекты, поэтому растут число объектов, объём памяти и нагрузка на сборщик мусора.
Однако интернирование всех входных строк опасно для данных с высокой кардинальностью: например, для идентификаторов пользователей или произвольных URL. Уникальные значения не могут разделять содержимое, а служебные структуры интернирования и дополнительные операции всё равно потребуют ресурсов.
sys.intern(s) возвращает строку, связанную с каноническим представлением этого значения. При повторном вызове для равной строки возвращается тот же объект, поэтому в примере две строки "ok" могут проверяться через is. Для проверки содержимого в обычном коде всё равно следует использовать ==: идентичность строк не является общим контрактом Python.
Интернирование экономит память только на повторяющихся значениях: само содержимое строки хранится один раз, а разные ссылки указывают на общий объект. Оно не является сжатием и не уменьшает размер уникальных строк. Кроме того, вызов требует поиска в таблице интернированных строк, поэтому выигрыш нужно подтверждать измерениями.
Наиболее безопасен ограниченный словарь: статусы, уровни логирования, типы сообщений и другие значения, множество которых заранее невелико. Для известных вариантов иногда лучше использовать enum, числовые коды или отображение строки в компактное значение. Такие варианты могут уменьшить память сильнее, но требуют изменения интерфейсов и обработки неизвестных значений.
Нельзя считать интернирование универсальным способом ускорить сравнения. В реальных структурах стоимость хеширования, поиска в словарях, декодирования и выделения объектов может быть выше выигрыша от is. Решение следует принимать по профилю памяти и времени на репрезентативном наборе данных.
Сервис обрабатывает поток логов. Поле level принимает четыре значения, но первоначально каждая запись декодирует его в новую строку. Снимок памяти показывает большое количество коротких одинаковых строк.
Рассматривались три варианта. Оставить строки без изменений проще всего, но это сохраняет дублирование. Заменить уровни на числа экономнее и быстрее для дальнейшей обработки, однако меняется формат внутренних API и усложняется диагностика. Интернировать только поле level проще внедрить без изменения схемы, но нужно гарантировать, что множество значений ограничено.
Выбрано выборочное интернирование уровней, а не всех строк записи. После изменения измерили пиковое потребление памяти и задержку обработки; решение оставили только при подтверждённом снижении памяти без неприемлемого роста CPU. Пользовательские идентификаторы и текст сообщений намеренно не интернировались, поскольку их множество не ограничено.
Можно ли после интернирования всегда заменять == на is?
Нет. is проверяет, является ли это ровно тот же объект, а == — равенство значений. Только для строк, которые гарантированно прошли одинаковое интернирование, идентичность может быть полезной внутренней оптимизацией; для внешних данных и общего кода нужно использовать ==.
Почему интернирование уникальных идентификаторов может ухудшить ситуацию?
Для каждой уникальной строки всё равно создаётся отдельный объект, поэтому выигрыша от совместного хранения содержимого нет. Дополнительно выполняется поиск в таблице интернирования и поддерживаются ссылки или служебные записи для управления этими значениями. При неограниченном потоке уникальных данных это увеличивает память и может добавить CPU-затраты.
Чем интернирование отличается от кэша преобразованных значений?
Интернирование канонизирует равные строки независимо от места их создания, а прикладной кэш обычно связывает входной ключ с результатом операции и может иметь ограничение размера, TTL или стратегию вытеснения. Интернирование подходит для небольшого стабильного множества повторяющихся строк; кэш лучше контролирует срок жизни и стоимость результатов, но требует явной политики управления.