2 отдельные стратегии мемоизации

Проверить первоначальная версия этой статьи в моем блоге разработчиков.

Когда вы впервые узнаете о мемоизации в JavaScript, это выглядит так: волшебный инструмент, который решит все ваши проблемы с производительностью. Люди начинают обращаться memoize над всем, не слишком задумываясь о том, что он на самом деле делает. (Я знаю, что сделал)

Существует множество различных библиотек для запоминания. npm, многие из них построены на действительно великих идеях. Я не собираюсь описывать здесь разные библиотеки, это получило бы очень длинную статью. Одна важная вещь, которую нужно понять, это то, что на самом деле есть две действительно разные стратегии для запоминания, и, скорее всего, в какой-то момент у вас будет вариант использования обоих в вашем приложении. Так что большую часть времени, вы должны использовать как минимум два разных помощника запоминания (или одну библиотеку с двумя разными конфигурациями).

Стратегия 1 – важны только последние данные

Только в этой стратегии один результат (и один вход) запоминается помощником. Если мемоизированная функция вызывается с новым (набором) аргументов, старый результат отбрасывается, а новый результат сохраняется.

const complexCalculation(input) = memoize(...);

complexCalculation(1); // NOT memoized
complexCalculation(1); // memoized
complexCalculation(2); // NOT memoized
complexCalculation(1); // NOT memoized
complexCalculation(2); // NOT memoized
complexCalculation(2); // memoized

Это отлично подходит для сценариев, в которых есть единственный источник правды, а результаты, основанные на новых входных данных, делают старые результаты недействительными. Например, для индексов по хранилищам Redux, преобразованию данных сеанса/пользователя и т. д. После обновления Redux я знаю, что ни один селектор не будет запрашивать старое хранилище, поэтому безопасно (и эффективно) отбросить старые результаты.

Преимущества:

  • Постоянное и предсказуемое использование памяти. Используемая память всегда не превышает размер самого последнего результата плюс размер последнего ввода.
  • Скорость. Здесь нет поиска по хеш-дереву, единственное влияние на производительность помощника мемоизации — это стоимость сравнения входных данных. Что, если использовать ссылочное равенство, практически равно нулю.
  • Простота. Эти библиотеки действительно просты в реализации и использовании. запомнить-один например, это буквально 36 строк (не минимизированного, легко читаемого) кода TypeScript. При его использовании вам не нужно беспокоиться об аннулировании кеша и связанных с ним препятствиях, он просто работай™.

Недостатки:

  • Не запоминает, когда несколько разных входных данных используются попеременно. Это может быть тот случай, когда несколько разных модулей вашего кода (например, несколько компонентов React или несколько экземпляров одного и того же компонента React) по-разному вызывают одну и ту же мемоизированную функцию.
  • Большинство этих библиотек сравнивают входные данные, используя ссылочное равенство (для скорости), что требует рассмотрения.

Примеры из запоминание отдельных элементов по умолчанию: запомнить-один, повторно выбрать, React.памяткаРеакт useMemo крюк.

Стратегия 2 — мемоизация на основе кэша

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

const complexCalculation(input) = memoize(...);

complexCalculation(1); // NOT memoized
complexCalculation(1); // memoized
complexCalculation(2); // NOT memoized
complexCalculation(1); // memoized
complexCalculation(2); // memoized
complexCalculation(2); // memoized

Сейчас существует гораздо большее разнообразие реализаций мемоизации на основе кеша, но в целом это куда более сложный случай и как мы все знаем:

С большой властью приходит большая ответственность.

Основное соображение при использовании мемоизации на основе кеша утечки памяти. Обычно функцию можно вызвать с бесконечным числом возможных аргументов. Если мы кэшируем результат каждого вызова в простой object или же Map при этом мы будем — если не будем осторожны — выделять постоянно растущий объем памяти. Вам либо нужно убедиться, что будет сделано только приемлемо небольшое количество различных вызовов, либо вам нужно реализовать/использовать хелпер мемоизации, который имеет какой-то вид политика выселения. (См. документацию по запоминающийся-неизменный который имеет большой запас различных базовых хранилищ для различных вариантов использования.) Это довольно простая задача, но в противном случае это может вызвать сильные головные боли. Проблемы, связанные с утечкой памяти, всегда трудно отладить.

Существует также соображение, хотите ли вы:

  • Придерживайтесь ссылочного равенства. Это очень быстро для поиска, но может привести к промахам кеша и созданию мусора при вызове с литералом встроенного объекта (например: memoizedFunction({someInput: 1}) всегда будет скучать)
  • Реализуйте некоторую глубокую проверку равенства, в основном используя JSON.stringify. Это решает описанную выше проблему, но создание строк JSON каждый раз происходит медленнее примерно на порядок и не будет обрабатывать функции в качестве аргументов.

Преимущества:

  • Запоминает несколько значений.
  • Можно настроить для использования равенства значений.
  • Труднее неправильно использовать и вызывать промахи (при использовании равенства значений)

Недостатки:

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

Примеры из запоминание на основе кеша:

  • быстро запоминать (по умолчанию используется JSON.stringify с простым объектом, который на самом деле медленный, поэтому интересный выбор имени)
  • повторно выбрать (сделано специально для повторного выбора, имеет несколько вариантов кеша)
  • _.запоминать (использует Map и ссылочное равенство по умолчанию)
  • запоминающийся-неизменный (имеет несколько мощных опций кеша)

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

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

Если у вас есть какие-либо отзывы или вы хотите узнать больше о теме со мной на сеансе наставничества 1: 1, не стесняйтесь обращаться ко мне в любое время!

Похожие записи

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *