Русский: Понимание мемоизации в JavaScript для повышения производительности
Мы стремимся улучшить производительность приложения,Memoization
даJavaScript
Метод, при котором ускоряются трудоемкие операции поиска за счет кэширования результатов и повторного использования кэша в следующей операции.
Здесь мы увидимmemoization
использования и как это может помочь оптимизировать производительность вашего приложения.
Мемоизация: основная идея
Если у нас есть операции с интенсивным использованием ЦП, мы можем оптимизировать использование, сохранив результат начальной операции в кеше. Если операция должна быть выполнена снова, мы не утруждаем себя повторным использованием нашего ЦП, потому что результат для того же результата где-то хранится, и мы просто возвращаем результат.
См. следующий пример:
function longOp(arg) {
if( cache has operation result for arg) {
return the cache
}
else {
假设执行一个耗时30分钟的操作
把结果存在`cache`缓存里
}
return the result
}
longOp('lp') // 因为第一次执行这个参数的操作,所以需要耗时30分钟
// 接下来会把结果缓存起来
longOp('bp') // 同样的第一次执行bp参数的操作,也需要耗时30分钟
// 同样会把结果缓存起来
longOp('bp') // 第二次出现了
// 会很快的把结果从缓存里取出来
longOp('lp') //也同样出现过了
// 快速的取出结果
С точки зрения использования ЦП, псевдофункция вышеlongOp
является трудоемкой функцией. Приведенный выше код будет кешировать результат первого раза, а последующие вызовы с тем же вводом будут извлекать результат из кеша, что позволит обойти потребление времени и ресурсов.
Вот пример квадратного корня:
function sqrt(arg) {
return Math.sqrt(arg);
}
log(sqrt(4)) // 2
log(sqrt(9)) // 3
Теперь мы можем использоватьmemoize
для обработки этой функции:
function sqrt(arg) {
if (!sqrt.cache) {
sqrt.cache = {}
}
if (!sqrt.cache[arg]) {
return sqrt.cache[arg] = Math.sqrt(arg)
}
return sqrt.cache[arg]
}
Как видите, результаты кэшируются вcache
в свойствах.
Запоминание: Исполнение
В предыдущем разделе мы добавили для функцииmemoization
.
Теперь мы можем создать отдельную функцию для запоминания любой функции. Мы называем эту функциюmemoize
.
function memoize(fn) {
return function () {
var args = Array.prototype.slice.call(arguments)
fn.cache = fn.cache || {};
return fn.cache[args] ? fn.cache[args] : (fn.cache[args] = fn.apply(this,args))
}
}
Мы видим, что этот код принимает другую функцию в качестве параметра и возвращает ее.
Чтобы использовать эту функцию, мы вызываемmemoize
Передайте функцию для кэширования в качестве аргумента.
memoizedFunction = memoize(funtionToMemoize)
memoizedFunction(args)
Теперь добавим приведенный выше пример к этому:
function sqrt(arg) {
return Math.sqrt(arg);
}
const memoizedSqrt = memoize(sqrt)
функция, которая возвращаетmemoizedSqrt
сейчасsqrt
изmemoized
Версия.
Давай позвоним:
//...
memoizedSqrt(4) // 2 calculated(计算)
memoizedSqrt(4) // 2 cached
memoizedSqrt(9) // 3 calculated
memoizedSqrt(9) // 3 cached
memoizedSqrt(25) // 5 calculated
memoizedSqrt(25) // 5 cached
мы можем поставитьmemoize
функция добавлена вFunction
прототип, так что каждая функция, определенная в нашем приложении, наследуетmemoize
функцию и вызовите ее.
Function.prototype.memoize = function() {
var self = this
return function () {
var args = Array.prototype.slice.call(arguments)
self.cache = self.cache || {};
return self.cache[args] ? self.cache[args] : (self.cache[args] = self(args))
}
}
Мы знаем, что все функции, определенные в JS, создаются изFunction.prototype
унаследовал. Поэтому добавьте кFunction.prototype
Все из доступно для всех функций, которые мы определяем.
Давайте попробуем еще раз сейчас:
function sqrt(arg) {
return Math.sqrt(arg);
}
// ...
const memoizedSqrt = sqrt.memoize()
log(memoizedSqrt(4)) // 2, calculated
log(memoizedSqrt(4)) // 2, returns result from cache
log(memoizedSqrt(9)) // 3, calculated
log(memoizedSqrt(9)) // 3, returns result from cache
log(memoizedSqrt(25)) // 5, calculated
log(memoizedSqrt(25)) // 5, returns result from cache
Memoization: Speed and Benchmarking
memoization
Цель — скорость, и он использует память для повышения скорости.
См. сравнение ниже:
имя файла:memo.js
:
function memoize(fn) {
return function () {
var args = Array.prototype.slice.call(arguments)
fn.cache = fn.cache || {};
return fn.cache[args] ? fn.cache[args] : (fn.cache[args] = fn.apply(this,args))
}
}
function sqrt(arg) {
return Math.sqrt(arg);
}
const memoizedSqrt = memoize(sqrt)
console.time("non-memoized call")
console.log(sqrt(4))
console.timeEnd("non-memoized call")
console.time("memoized call")
console.log(sqrt(4))
console.timeEnd("memoized call")
потомnode memo.js
Выход можно найти, вот я:
2
non-memoized call: 2.210ms
2
memoized call: 0.054ms
Можно заметить, что скорость значительно улучшилась.
Мемоизация: когда использовать
это здесь,memoization
Часто это сокращает время выполнения и влияет на производительность нашего приложения. Когда мы знаем, что набор входных данных приведет к определенному результату,memoization
Самый эффективный.
Следуя лучшим практикам, он должен быть реализован на чистых функциях.memoization
. Чистые функции возвращают все, что вводят, без побочных эффектов.
Помните, что это меняет место на скорость, поэтому лучше решить, стоит ли оно того для вас, это необходимо для некоторых сценариев.
При работе с рекурсивными функциямиMemoization
Наиболее эффективные рекурсивные функции используются для выполнения тяжелой работы, такой как рендеринг графического интерфейса, спрайты и физика анимации.
Мемоизация: когда не использовать
Когда это не чистая функция (выход не полностью зависит от входа).
Вариант использования: ряд Фибоначчи
Fibonacci
является одним из многих сложных алгоритмов, использующихmemoization
Эффект оптимизации очевиден.
1,1,2,3,5,8,13,21,34,55,89
Каждое число является суммой двух предыдущих чисел.
Теперь мы используемjs
выполнить:
function fibonacci(num) {
if (num == 1 || num == 2) {
return 1
}
return fibonacci(num-1) + fibonacci(num-2)
}
Если num превышает 2, эта функция является рекурсивной. Он рекурсивно вызывает себя в убывающей манере.
log(fibonacci(4)) // 3
Давайте проверим эффективность запуска Фибоначчи в сравнении с запомненной версией.memo.js
документ:
function memoize(fn) {
return function () {
var args = Array.prototype.slice.call(arguments)
fn.cache = fn.cache || {};
return fn.cache[args] ? fn.cache[args] : (fn.cache[args] = fn.apply(this,args))
}
}
function fibonacci(num) {
if (num == 1 || num == 2) {
return 1
}
return fibonacci(num-1) + fibonacci(num-2)
}
const memFib = memoize(fibonacci)
console.log('profiling tests for fibonacci')
console.time("non-memoized call")
console.log(memFib(6))
console.timeEnd("non-memoized call")
console.time("memoized call")
console.log(memFib(6))
console.timeEnd("memoized call")
Следующий звонок:
$ node memo.js
profiling tests for fibonacci
8
non-memoized call: 1.027ms
8
memoized call: 0.046ms
Можно обнаружить, что при небольшом числе временной разрыв настолько велик.
Вышеприведенное является ссылкой на оригинальный текст, а следующее является личным впечатлением.
Что я могу сказать, я впервые об этом подумалreact
изmemo
компонент (УведомлениеЗдесь текущая версия (16.6.3
) имеет дваmemo
,одинReact.memo, а другой естьReact.useMemo, мы говорим о здесьuseMemo
), верим в следующееreact
динамический знатьuseMemo
новыйhooks api
, и этоapi
действует наfunction
Компоненты, официальная документация пишет, что это можно оптимизировать, чтобы оптимизировать трудоемкую работу каждого рендера.
СмотретьДокументация здесьтоже вполне понимаю. видел сегодняmedium
этой статьи, чувства иreact memo
Все в порядке, я посмотрюисходный код, оказалось действительно таким же, как описано в этой статье.
export function useMemo<T>(
nextCreate: () => T,
inputs: Array<mixed> | void | null,
): T {
currentlyRenderingFiber = resolveCurrentlyRenderingFiber(); //返回一个变量
workInProgressHook = createWorkInProgressHook(); // 返回包含memoizedState的hook对象
const nextInputs =
inputs !== undefined && inputs !== null ? inputs : [nextCreate]; // 需要保存下来的inputs,用作下次取用的key
const prevState = workInProgressHook.memoizedState; // 获取之前缓存的值
if (prevState !== null) {
const prevInputs = prevState[1];
// prevState不为空,并且取出上次存的`key`, 然后下面判断(前后的`key`是不是同一个),如果是就直接返回,否则继续向下
if (areHookInputsEqual(nextInputs, prevInputs)) {
return prevState[0];
}
}
const nextValue = nextCreate(); //执行useMemo传入的第一个参数(函数)
workInProgressHook.memoizedState = [nextValue, nextInputs]; // 存入memoizedState以便下次对比使用
return nextValue;
}
кэшированный (workInProgressHook.memoizedState
то естьhook
возвращенный объект и содержитmemoizedState
, чтобы сравнить до и послеinputs
то же самое, затем сделайте это снова) и поддерживает передачу второго параметра массива какkey
.
В самом деле,useMemo
используется в этой статьеmemoization
для повышения производительности.
На самом деле, из официальной документации я знаю, что эти два понятия связаны :cry: :
Передайте функцию «создать» и массив входных данных. useMemo будет пересчитывать запомненное значение только при изменении одного из входных данных. Эта оптимизация помогает избежать дорогостоящих вычислений при каждом рендеринге.
Личная запись об обучении - добро пожаловать, звезда и часы, чтобы учиться вместе