Узнайте о мемоизации в JavaScript для повышения производительности и ознакомьтесь с приложениями React.

внешний интерфейс алгоритм JavaScript React.js

Русский: Понимание мемоизации в JavaScript для повышения производительности

Китайский: изучать память 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 будет пересчитывать запомненное значение только при изменении одного из входных данных. Эта оптимизация помогает избежать дорогостоящих вычислений при каждом рендеринге.

Личная запись об обучении - добро пожаловать, звезда и часы, чтобы учиться вместе