O que é memorização e por que isso é importante?
whiteMocca / Shutterstock. com
Memoização é uma técnica de programação que acelera o desempenho armazenando em cache os valores de retorno de chamadas de função caras. Um “ memorizado ” A função produzirá imediatamente um valor pré-calculado se forem dados dados que já foram vistos antes.
Memoização é uma forma específica de armazenamento em cache que se presta a cenários onde uma função cara é executada repetidamente, às vezes com os mesmos argumentos. Contanto que a função seja pura para que sempre produza o mesmo valor de um determinado conjunto de entradas, memorizá-la pode aumentar a eficiência e reduzir o desperdício de ciclos de CPU.
Você encontrará mais frequentemente memoização em linguagens de programação funcionais. A técnica tem ampla utilidade, entretanto. É um conceito abstrato que você pode incorporar em qualquer código recursivo. Estamos usando JavaScript para este artigo, mas você pode reescrever os exemplos em sua linguagem de trabalho.
Um exemplo simples
Aqui está uma função simples que gera o fatorial de um determinado número inteiro:
const factorial = n = > & # 123; if & # 40; n === 0 & # 41; return1; elsereturn & # 40; factorial & # 40; n -1 & # 41; * n & # 41 ;; & # 125 ;;
O cálculo fatorial é recursivo, já que factorial () chama a si mesmo dentro da condição else. O cálculo também é puro, pois qualquer valor de n sempre retornará o mesmo valor. fatorial (5) é 120, independentemente do estado do programa.
Publicidade
Devido à natureza recursiva da função, calcular o fatorial de vários números grandes incorre em operações perdidas:
const x = fatorial & # 40; 100 & # 41 ;; const y = fatorial & # 40; 50 & # 41 ;;
Todos os cálculos necessários para calcular y já foram realizados como parte do cálculo de x. Se fatorial () fosse para armazenar em cache suas entradas e suas saídas correspondentes, o cálculo de y poderia ser significativamente acelerado.
Memorizando a função fatorial
Esta é uma abordagem básica que memoriza a função factorial ():
const cache = & # 123; & # 125 ;; const fatorial = n = > & # 123; se & # 40; cache & # 91; n & # 93; & # 41; & # 123; cache de retorno & # 91; n & # 93 ;; & # 125; deixe o valor; se & # 40; n === 0 & # 41; value = 1; else value = & # 40; fatorial & # 40; n -1 & # 41; * n & # 41 ;; cache & # 91; n & # 93; = valor; valor de retorno; & # 125 ;;
Agora, há um objeto de cache que factorial () usa para registrar seus valores de saída. Cada vez que a função é chamada, ela primeiro verifica se viu a entrada n. Se tiver, ele pode entrar em curto-circuito imediatamente e retornar o valor em cache. Caso contrário, o cálculo recursivo continuará normalmente, mas as execuções subsequentes usando o mesmo número serão aceleradas.
Agora, computar fatorial (50) após fatorial (100) será muito mais eficiente. O fatorial de 50 seria calculado como parte do fatorial de 100, então a função poderia retornar o valor quase instantaneamente.
Uma solução mais geral
Embora o código acima funcione, ele é específico da função factorial (). Se você estivesse usando outras funções semelhantes, você &’ precisaria adicionar manualmente o código de cache para cada uma.
Uma solução mais geral permitiria que você envolvesse qualquer função com uma função de ordem superior que fornecesse a capacidade de memorização:
const memoize = fn = > & # 123; const cache = & # 123; & # 125 ;; return & # 40; ... args & # 41; = > & # 123; const argsString = JSON. stringify & # 40; args & # 41 ;; if & # 40;! cache & # 91; argsString & # 93; & # 41; & # 123; cache & # 91; argsString & # 93; = fn & # 40; ... args & # 41 ;; & # 125; cache de retorno & # 91; argsString & # 93 ;; & # 125; & # 125 ;;
Agora, você pode ajustar a função fatorial () original:
const factorial = memoize & # 40; n = > & # 123; if & # 40; n === 0 & # 41; return1; elsereturn & # 40; factorial & # 40; n -1 & # 41; * n & # 41 ;; & # 125; & # 41 ;;
Publicidade
Envolvendo factorial () com memoize (), ele ganha recursos de memoização automática. A função wrapper retorna uma nova função que intercepta todas as chamadas para factorial (), transforma seus argumentos em string e verifica se eles foram vistos antes. Em caso afirmativo, o valor de retorno anterior é reutilizado sem chamar a função real. Quando novos argumentos são vistos, a função é chamada e sua saída é adicionada ao cache.
O wrapper usa a sintaxe de parâmetros restantes do JavaScript para aceitar um número variável de argumentos. Isso significa que funcionará com qualquer função JavaScript. Essa abordagem usa JSON. stringify () para criar a representação de string dos argumentos, portanto, deve-se tomar cuidado se você estiver chamando uma função com objetos complexos, que não podem ser totalmente representados como JSON.
Quando não usar a memorização?
A memorização pode fornecer melhorias significativas de desempenho, especialmente para operações matematicamente pesadas. No entanto, não é uma técnica para usar em qualquer lugar. Nem todas as funções devem ser memorizadas, pois você pode acabar prejudicando o desempenho em alguns casos.
Ao envolver uma função com memoize (), você está forçando uma comparação dos argumentos de entrada cada vez que a função é chamada. Isso por si só consumirá algum tempo de CPU. O wrapper de exemplo acima executa JSON. stringify () sempre que a função é chamada, adicionando uma nova sobrecarga.
A memorização é apropriada para funções onde há uma grande chance de que os mesmos valores de entrada sejam vistos regularmente. Se você raramente chamar uma função com os mesmos argumentos, as ocorrências do cache serão raras e você poderá perder desempenho com a serialização e comparação de argumentos. Manter o cache também aumenta o uso de memória, pois todas as entradas e saídas anteriores precisam ser retidas.
Portanto, você deve avaliar totalmente a função de cada função em seu programa antes de decidir memorizar. Compare o desempenho com e sem memorização. Às vezes, pode ser mais benéfico confiar nas próprias otimizações do navegador.
Publicidade
Finalmente, tenha em mente que algumas funções não podem ser memoizadas. A técnica só funciona com funções puras — se sua função alcança variáveis globais ou algum outro estado do aplicativo, ela não deve ser memorizada!
const functionUsingGlobalState = n = > & # 123; if & # 40; n === window. scrollY & # 41; returntrue; elsereturn functionUsingGlobalState & # 40; n -1 & # 41 ;; & # 125;
Memorizar a função acima pode gerar resultados inesperados após a primeira execução. Os valores de n serão armazenados em cache usando a versão original de window. scrollY. O wrapper de memoização retornará a saída em cache, mesmo se window. scrollY tiver sido alterado.
Resumo
Memoização é uma forma de armazenamento em cache que acelera o desempenho de operações recursivas repetitivas. É uma técnica de programação funcional que pode ser implementada como um wrapper genérico para qualquer função pura.
Você encontrará com mais freqüência a memoização em funções frequentemente chamadas que realizam operações computacionalmente pesadas. Isso ajuda a evitar o desperdício, eliminando a necessidade de recalcular valores que já foram produzidos como parte de uma chamada anterior.
Os benefícios da memoização serão menos aparentes em funções que são simples no início ou raramente chamadas. Em alguns casos, o uso impróprio de memoização pode realmente prejudicar o desempenho, portanto, não memo cegamente todas as funções em seu aplicativo.
Nenhum comentário