Memory footprint

Базовая модель потребления памяти в Intl

Intl в JavaScript опирается на ICU (International Components for Unicode) — крупную библиотеку локализации, включающую языковые правила, таблицы форматирования и региональные данные. Основная часть памяти, связанной с Intl, не находится в самих объектах JavaScript, а скрыта в нативном слое движка (V8, JavaScriptCore, SpiderMonkey).

Каждый объект Intl-форматтера (Intl.DateTimeFormat, Intl.NumberFormat, Intl.Collator, Intl.PluralRules, Intl.Segmenter) представляет собой тонкую JS-обёртку над заранее подготовленной структурой ICU. Поэтому ключевая особенность памяти заключается в разделении:

  • JS-объект — лёгкий, содержит ссылку на нативный форматтер
  • ICU-объект — тяжёлый, хранит локализованные таблицы и правила
  • Кэш движка — повторно используемые экземпляры и данные локалей

Стоимость создания форматтера

Создание любого форматтера является относительно дорогой операцией. При инициализации происходит:

  • разбор локали (locale resolution algorithm)
  • загрузка правил форматирования из ICU
  • построение внутренних структур (числовые шаблоны, календарные данные, правила сегментации)
  • инициализация опций форматирования

Память при этом выделяется не только под сам объект, но и под набор данных локали, который может занимать значительный объём в зависимости от языка.

Например, en, fr, de имеют сравнительно компактные данные, тогда как локали с богатой морфологией и сложными правилами форматирования (например, ja, zh, ar) требуют больше ICU-структур.

Разделяемость данных и скрытые кэши

Современные JS-движки используют агрессивное кэширование ICU-данных. Это означает:

  • повторное создание Intl.NumberFormat('en-US') не всегда создаёт новый набор данных
  • часто используется ссылка на уже загруженные правила локали
  • часть структур живёт в глобальном ICU-кэше процесса

Однако кэширование имеет ограничения:

  • разные наборы опций (например, currency, compact, signDisplay) могут порождать новые экземпляры
  • изменение параметров форматирования ломает возможность повторного использования
  • в некоторых движках кэш ограничен по размеру и вытесняет старые записи

Таким образом, память распределяется не линейно от числа объектов, а от комбинаций локаль + опции.

Память и Intl.DateTimeFormat

Intl.DateTimeFormat относится к наиболее тяжёлым форматтерам.

Причины:

  • наличие календарных систем (Gregorian, Islamic, Buddhist и др.)
  • таблицы названий месяцев, дней недели, периодов времени
  • правила часовых поясов
  • поддержка форматных шаблонов (long, short, narrow)

Дополнительно хранится информация о:

  • локализации числовых систем (Latin, Arabic-Indic)
  • форматах времени (12/24-hour)
  • специфике отображения дат (order, separators)

При этом экземпляр форматтера может удерживать значительный объём ICU-данных даже при отсутствии активного использования.

Intl.NumberFormat и зависимость от конфигурации

Intl.NumberFormat демонстрирует более вариативную модель памяти.

Основные факторы роста памяти:

  • style: decimal, currency, percent, unit
  • notation: standard, scientific, engineering, compact
  • currency display rules
  • minimumFractionDigits / maximumFractionDigits
  • locale-specific grouping rules

Особенно заметен рост памяти при использовании compact notation, поскольку требуются таблицы сокращений чисел (K, M, B или локальные аналоги).

Дополнительно:

  • валютные правила требуют загрузки ISO-4217 справочников
  • unit formatting включает таблицы единиц измерения

Intl.Collator и лингвистические таблицы

Intl.Collator является одним из наиболее тяжёлых по внутренним структурам компонентов.

Он включает:

  • правила сортировки строк (DUCET + tailoring)
  • таблицы нормализации Unicode
  • алгоритмы сравнения с учётом диакритики и регистра
  • локализованные исключения сортировки

Внутренне ICU хранит значительные таблицы весов символов, что делает Collator особенно затратным по памяти при поддержке большого числа локалей.

Особенность:

  • создание множества Collator с разными usage и sensitivity может приводить к увеличению количества уникальных таблиц
  • частично разделяемые данные уменьшают нагрузку, но не устраняют её полностью

Intl.PluralRules и компактность структуры

Intl.PluralRules выглядит лёгким по сравнению с другими форматтерами, однако:

  • хранит правила множественных форм для каждой локали
  • включает CLDR-таблицы категорий plural rules (one, few, many, other и т.д.)
  • зависит от типа (cardinal/ordinal)

Память растёт линейно от количества поддерживаемых категорий в конкретной локали, но остаётся существенно ниже, чем у DateTimeFormat или Collator.

Intl.Segmenter и сегментационные таблицы

Intl.Segmenter (разделение текста на слова, предложения, графемы) использует ICU Break Iterator.

Основные источники памяти:

  • правила разбиения Unicode text boundaries
  • языковые корректировки (особенно для CJK языков)
  • state machines для анализа последовательностей символов

Наиболее тяжёлые случаи:

  • японский и китайский тексты (отсутствие пробелов требует сложных моделей сегментации)
  • emoji sequences (ZWJ, grapheme clusters)

Модель жизненного цикла и влияние GC

JS-объекты Intl управляются garbage collector’ом, но нативные ICU-структуры могут:

  • жить дольше JS-объекта из-за внутреннего кэша движка
  • переиспользоваться между различными экземплярами
  • освобождаться только при деградации кэша или завершении процесса

Это создаёт асимметрию:

  • удаление JS-ссылки не гарантирует мгновенного освобождения памяти ICU
  • повторное создание форматтера может не приводить к новому выделению памяти

Различия браузеров и Node.js

Потребление памяти зависит от реализации:

V8 (Node.js, Chrome):

  • агрессивное ICU-кэширование
  • оптимизация повторного использования форматтеров
  • единый пул локалей

JavaScriptCore (Safari):

  • более консервативная модель кеширования
  • частичное разделение ICU-структур

SpiderMonkey (Firefox):

  • отдельные оптимизации под колляцию и сегментацию
  • возможны отличия в размере кэшей

ICU data footprint и сборка движков

Размер ICU-данных зависит от сборки JavaScript-движка:

  • full ICU build — поддержка всех локалей (десятки мегабайт)
  • small ICU — ограниченный набор языков
  • system ICU — использование системных библиотек локализации

При этом:

  • рост числа поддерживаемых языков прямо увеличивает memory footprint
  • tree-shaking на уровне JavaScript невозможен, так как ICU — нативный слой

Кэширование экземпляров форматтеров в прикладном коде

Создание форматтеров внутри горячих путей приводит к избыточному потреблению памяти из-за:

  • повторной инициализации ICU структур
  • увеличения количества уникальных комбинаций опций
  • давления на внутренние кэши движка

С точки зрения памяти наиболее критично:

  • создание Intl.* внутри циклов
  • динамическое изменение locale/options
  • отсутствие повторного использования экземпляров

Переиспользование одного экземпляра:

  • снижает количество ICU-аллоцированных структур
  • уменьшает давление на GC
  • стабилизирует memory footprint процесса

format vs formatToParts и влияние на память

formatToParts увеличивает временные аллокации:

  • создаются промежуточные структуры (массивы частей)
  • увеличивается нагрузка на GC при массовом использовании
  • возрастает peak memory usage

format в большинстве случаев менее затратен по памяти, так как возвращает строку без промежуточной структуризации результата.

Общая модель роста памяти

Память, связанная с Intl, можно представить как сумму:

  • базовый ICU runtime footprint
  • кэш локалей
  • уникальные комбинации format options
  • активные экземпляры форматтеров
  • временные структуры форматирования

Ключевая особенность — нелинейный рост при увеличении разнообразия локалей и опций, а не количества вызовов форматирования.