Бенчмарки Intl API

Intl API в JavaScript опирается на ICU (International Components for Unicode), поэтому его производительность определяется не только движком JavaScript, но и слоем нативных библиотек. В результате бенчмарки Intl требуют аккуратного подхода: измеряется не только скорость выполнения JS-кода, но и стоимость переходов в нативный слой, аллокаций локализационных структур и кеширования внутри движка.

Ключевые компоненты, влияющие на производительность:

  • инициализация форматтеров (Intl.NumberFormat, Intl.DateTimeFormat, Intl.Collator);
  • повторное использование экземпляров;
  • вызовы методов форматирования (format, formatToParts);
  • обработка локалей и опций;
  • внутренние кэши ICU и движка (V8, SpiderMonkey, JavaScriptCore).

Инициализация форматтеров как самая дорогая операция

Создание экземпляра Intl-объекта является одной из наиболее затратных операций в API. Причина заключается в необходимости:

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

Пример:

const fmt = new Intl.NumberFormat("ru-RU", {
  style: "currency",
  currency: "RUB"
});

Стоимость этого вызова существенно выше, чем последующее использование:

fmt.format(123456.78);
fmt.format(98765.43);

В бенчмарках часто наблюдается порядок различий:

  • создание: десятки–сотни микросекунд (иногда миллисекунды при сложных локалях);
  • форматирование: единицы микросекунд.

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

Наиболее значимый фактор ускорения Intl-кода — переиспользование объектов форматирования.

Создание внутри горячих циклов приводит к деградации производительности:

for (let i = 0; i < 100000; i++) {
  const f = new Intl.NumberFormat("en-US");
  f.format(i);
}

Оптимизированный вариант:

const f = new Intl.NumberFormat("en-US");

for (let i = 0; i < 100000; i++) {
  f.format(i);
}

Разница в бенчмарках обусловлена устранением повторной инициализации ICU-структур и аллокаций.


Стоимость format vs formatToParts

Метод formatToParts заметно тяжелее, чем format, так как возвращает структурированный массив:

const f = new Intl.NumberFormat("en-US");

f.format(12345); 
f.formatToParts(12345);

Причины дополнительной нагрузки:

  • создание массива объектов;
  • разбиение строки на токены;
  • дополнительные аллокации для каждой части (integer, decimal, literal и т.д.).

В бенчмарках разница может достигать 2–5× в зависимости от движка и типа данных.


Влияние локали на производительность

Не все локали обрабатываются одинаково быстро. Сложность зависит от:

  • длины цепочки fallback локалей;
  • наличия специализированных правил (например, арабские, индийские системы чисел);
  • размера данных ICU для конкретного языка.

Примеры:

  • "en-US" — минимальная нагрузка;
  • "ru-RU" — умеренная;
  • "ar-EG" — более сложная обработка числовых систем;
  • составные локали "zh-Hant-TW-u-ca-chinese" — повышенная стоимость резолва.

Бенчмарки часто демонстрируют разницу не в форматировании, а именно в создании объекта.


Влияние опций на скорость

Опции напрямую влияют на внутренние ветвления ICU.

NumberFormat

new Intl.NumberFormat("en-US", {
  style: "currency",
  currency: "USD",
  minimumFractionDigits: 2
});

Каждое дополнительное правило увеличивает сложность:

  • style: "currency" добавляет валютные таблицы;
  • compactDisplay активирует компактные формы;
  • signDisplay усложняет логику знаков.

DateTimeFormat

new Intl.DateTimeFormat("en-US", {
  dateStyle: "full",
  timeStyle: "long"
});

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


Кэширование внутри движков

Современные JS-движки применяют частичное кэширование:

  • V8 хранит внутренние кеши форматтеров при повторном создании с одинаковыми параметрами;
  • ICU кеширует локализационные данные;
  • оптимизации скрывают часть стоимости повторных вызовов.

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


Бенчмаркинг через microbenchmark подход

Измерение Intl требует аккуратности из-за влияния JIT и оптимизаций.

Типичный шаблон:

const start = performance.now();

for (let i = 0; i < 1000000; i++) {
  formatter.format(i);
}

const end = performance.now();

console.log(end - start);

Проблемы такого подхода:

  • отсутствие прогрева JIT;
  • влияние GC;
  • системные шумы;
  • оптимизация “мертвого кода” (dead code elimination).

Разогрев (warm-up) и стабильность измерений

Перед измерением критически важно выполнение warm-up фаз:

for (let i = 0; i < 10000; i++) {
  formatter.format(i);
}

Это позволяет:

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

Без warm-up результаты часто завышают стоимость операций.


format vs ручная конкатенация

Intl API часто сравнивается с ручным форматированием строк:

// Intl
formatter.format(12345);

// manual
"12345".replace(/\B(?=(\d{3})+(?!\d))/g, ",");

Бенчмарки показывают, что:

  • ручные методы могут быть быстрее на простых кейсах;
  • Intl выигрывает на сложных локалях;
  • ручная реализация не масштабируется по локалям и валютам.

Intl.Collator и сортировка

Сортировка с использованием Intl.Collator имеет собственную модель затрат:

const collator = new Intl.Collator("en", { sensitivity: "base" });

["z", "a", "c"].sort(collator.compare);

Особенности бенчмаркинга:

  • compare вызывается многократно внутри sort;
  • стоимость сравнения выше обычного a - b;
  • локалезависимая логика требует ICU правил.

Оптимизация достигается созданием одного collator на всю операцию сортировки.


Аллокации и влияние на GC

Intl API генерирует:

  • временные строки;
  • массивы частей (formatToParts);
  • внутренние ICU структуры;
  • объекты опций при нормализации.

Частые вызовы приводят к росту нагрузки на garbage collector, особенно в сценариях:

  • форматирование больших массивов данных;
  • SSR (server-side rendering);
  • логирование финансовых потоков.

Сравнение движков

Поведение Intl API зависит от реализации:

  • V8 (Node.js, Chrome) — агрессивные оптимизации кеширования;
  • SpiderMonkey (Firefox) — сильная интеграция с ICU;
  • JavaScriptCore (Safari) — оптимизации под Apple ICU stack.

Различия проявляются в:

  • скорости создания объектов;
  • стоимости formatToParts;
  • эффективности кешей локалей.

Практические наблюдения из бенчмарков

Типичные результаты микробенчмарков:

  • Intl.NumberFormat.format быстрее formatToParts в 2–6 раз;
  • повторное использование форматтера ускоряет код в десятки раз;
  • создание форматтера внутри цикла даёт экспоненциальную деградацию;
  • локали с простой структурой быстрее сложных составных локалей;
  • DateTimeFormat обычно дороже NumberFormat.

Узкие места в реальных приложениях

Основные источники замедления:

  • многократное создание Intl-объектов;
  • использование сложных опций без необходимости;
  • частые вызовы formatToParts;
  • сортировка больших массивов через Intl.Collator без кеширования;
  • генерация локализованных строк в горячих циклах.

Оптимизация почти всегда сводится к:

  • выносу Intl-объектов из циклов;
  • уменьшению количества уникальных конфигураций;
  • минимизации структурных преобразований результата.