您现在的位置是:网站首页 > 记忆模式(Memoization)的性能优化文章详情

记忆模式(Memoization)的性能优化

记忆模式(Memoization)的性能优化

记忆模式是一种通过缓存函数计算结果来避免重复计算的优化技术。当函数被再次调用时,如果参数相同,则直接返回缓存的结果,而不是重新执行计算。这种模式特别适用于计算密集型或递归函数,可以显著提升性能。

记忆模式的实现原理

记忆模式的核心思想是建立一个缓存对象,将函数的输入参数作为键,计算结果作为值存储起来。每次函数调用时,首先检查缓存中是否存在对应的计算结果,如果存在则直接返回,否则执行计算并将结果存入缓存。

function memoize(fn) {
  const cache = {};
  return function(...args) {
    const key = JSON.stringify(args);
    if (cache[key] !== undefined) {
      return cache[key];
    }
    const result = fn.apply(this, args);
    cache[key] = result;
    return result;
  };
}

这个基本实现有几个关键点:

  1. 使用闭包保存缓存对象
  2. 将参数序列化为字符串作为缓存键
  3. 只有在缓存未命中时才执行原函数

记忆模式的应用场景

递归函数优化

斐波那契数列计算是记忆模式的经典用例:

function fibonacci(n) {
  if (n <= 1) return n;
  return fibonacci(n - 1) + fibonacci(n - 2);
}

const memoizedFibonacci = memoize(function(n) {
  if (n <= 1) return n;
  return memoizedFibonacci(n - 1) + memoizedFibonacci(n - 2);
});

未经记忆化的版本时间复杂度为O(2^n),而记忆化后降为O(n),性能提升显著。

复杂计算缓存

对于需要复杂数学运算的函数:

function expensiveCalculation(x, y) {
  // 模拟耗时计算
  for(let i = 0; i < 1000000000; i++) {}
  return x * y + x / y;
}

const memoizedCalculation = memoize(expensiveCalculation);

API响应缓存

在需要频繁调用相同API的场景:

async function fetchUserData(userId) {
  const response = await fetch(`/api/users/${userId}`);
  return response.json();
}

const memoizedFetch = memoize(fetchUserData);

高级记忆化技术

自定义缓存键生成

默认的JSON.stringify可能不够灵活,可以自定义键生成函数:

function memoize(fn, keyGenerator) {
  const cache = new Map();
  return function(...args) {
    const key = keyGenerator ? keyGenerator(...args) : JSON.stringify(args);
    if (cache.has(key)) {
      return cache.get(key);
    }
    const result = fn.apply(this, args);
    cache.set(key, result);
    return result;
  };
}

基于WeakMap的缓存

对于对象参数,使用WeakMap可以避免内存泄漏:

function memoizeWeak(fn) {
  const cache = new WeakMap();
  return function(arg) {
    if (cache.has(arg)) {
      return cache.get(arg);
    }
    const result = fn.call(this, arg);
    cache.set(arg, result);
    return result;
  };
}

LRU缓存策略

限制缓存大小,使用最近最少使用策略:

function memoizeLRU(fn, limit = 100) {
  const cache = new Map();
  return function(...args) {
    const key = JSON.stringify(args);
    if (cache.has(key)) {
      const value = cache.get(key);
      cache.delete(key);
      cache.set(key, value);
      return value;
    }
    const result = fn.apply(this, args);
    if (cache.size >= limit) {
      const firstKey = cache.keys().next().value;
      cache.delete(firstKey);
    }
    cache.set(key, result);
    return result;
  };
}

React中的记忆化应用

React提供了useMemo和useCallback来实现组件级别的记忆化:

function ExpensiveComponent({ data }) {
  const processedData = useMemo(() => {
    return data.map(item => ({
      ...item,
      fullName: `${item.firstName} ${item.lastName}`,
      age: new Date().getFullYear() - item.birthYear
    }));
  }, [data]);

  return <div>{/* 使用processedData */}</div>;
}

对于事件处理函数:

function InteractiveComponent() {
  const handleClick = useCallback(() => {
    console.log('Clicked');
  }, []);

  return <button onClick={handleClick}>Click me</button>;
}

记忆模式的局限性

  1. 内存消耗:缓存会占用内存,特别是缓存大量结果时
  2. 参数变化:对于频繁变化的参数,缓存命中率低,反而增加开销
  3. 纯函数要求:记忆化只适用于纯函数,即相同输入总是产生相同输出
  4. 对象比较:对象参数需要深度比较或自定义比较逻辑

性能优化实践

选择性记忆化

不是所有函数都适合记忆化,应该针对性地选择:

// 适合记忆化
function calculateDistance(x1, y1, x2, y2) {
  return Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2));
}

// 不适合记忆化
function generateRandomId() {
  return Math.random().toString(36).substr(2, 9);
}

缓存失效策略

实现缓存过期机制:

function memoizeWithExpiry(fn, ttl = 60000) {
  const cache = {};
  return function(...args) {
    const key = JSON.stringify(args);
    const now = Date.now();
    if (cache[key] && now - cache[key].timestamp < ttl) {
      return cache[key].value;
    }
    const result = fn.apply(this, args);
    cache[key] = {
      value: result,
      timestamp: now
    };
    return result;
  };
}

性能测试对比

使用console.time进行简单性能测试:

function testPerformance() {
  // 普通斐波那契
  console.time('normal');
  fibonacci(40);
  console.timeEnd('normal');
  
  // 记忆化斐波那契
  console.time('memoized');
  memoizedFibonacci(40);
  console.timeEnd('memoized');
}

记忆模式与其他模式的结合

与工厂模式结合

创建可配置的记忆化函数工厂:

function createMemoizer(options = {}) {
  const {
    maxSize = Infinity,
    ttl = Infinity,
    keyGenerator = JSON.stringify
  } = options;
  
  return function memoize(fn) {
    const cache = new Map();
    const timers = new Map();
    
    return function(...args) {
      const key = keyGenerator(...args);
      
      // 检查缓存是否存在且未过期
      if (cache.has(key)) {
        if (Date.now() - cache.get(key).timestamp < ttl) {
          return cache.get(key).value;
        }
        // 清除过期缓存
        cache.delete(key);
        if (timers.has(key)) {
          clearTimeout(timers.get(key));
          timers.delete(key);
        }
      }
      
      // 执行函数并缓存结果
      const result = fn.apply(this, args);
      cache.set(key, {
        value: result,
        timestamp: Date.now()
      });
      
      // 设置缓存过期
      if (ttl < Infinity) {
        timers.set(key, setTimeout(() => {
          cache.delete(key);
          timers.delete(key);
        }, ttl));
      }
      
      // 检查缓存大小
      if (cache.size > maxSize) {
        const oldestKey = cache.keys().next().value;
        cache.delete(oldestKey);
        if (timers.has(oldestKey)) {
          clearTimeout(timers.get(oldestKey));
          timers.delete(oldestKey);
        }
      }
      
      return result;
    };
  };
}

与装饰器模式结合

在支持装饰器的环境中:

@memoize
class Calculator {
  static add(a, b) {
    console.log('Calculating...');
    return a + b;
  }
  
  @memoize
  multiply(a, b) {
    console.log('Calculating...');
    return a * b;
  }
}

浏览器缓存API的记忆化应用

利用localStorage实现持久化记忆:

function memoizeWithStorage(fn, keyPrefix = 'memo_') {
  return async function(...args) {
    const key = keyPrefix + JSON.stringify(args);
    try {
      const cached = localStorage.getItem(key);
      if (cached) {
        return JSON.parse(cached);
      }
      const result = await fn.apply(this, args);
      localStorage.setItem(key, JSON.stringify(result));
      return result;
    } catch (e) {
      console.error('Storage error:', e);
      return fn.apply(this, args);
    }
  };
}

记忆模式在函数式编程中的应用

在函数式编程中,记忆化可以保持函数的纯粹性:

// 柯里化函数与记忆化结合
function curryMemoize(fn) {
  const cache = new Map();
  const curried = (...args) => {
    if (args.length >= fn.length) {
      const key = JSON.stringify(args);
      if (cache.has(key)) return cache.get(key);
      const result = fn(...args);
      cache.set(key, result);
      return result;
    }
    return (...moreArgs) => curried(...args, ...moreArgs);
  };
  return curried;
}

const memoizedAdd = curryMemoize((a, b, c) => a + b + c);
memoizedAdd(1)(2)(3); // 计算并缓存
memoizedAdd(1)(2)(3); // 从缓存读取

我的名片

网名:~川~

岗位:console.log 调试员

坐标:重庆市-九龙坡区

邮箱:cc@qdcc.cn

沙漏人生

站点信息

  • 建站时间:2013/03/16
  • 本站运行
  • 文章数量
  • 总访问量
微信公众号
每次关注
都是向财富自由迈进的一步