ZuB1M1H.png

昨天,我们完成了“依赖清理”机制,让 effect 能够正确处理动态变化的依赖关系。然而,这也带来了一个新的性能问题:当依赖频繁变化时,系统需要不断地创建和销毁 Link 节点。每次建立依赖关系都会触发内存分配,频繁的分配/释放会导致:

  • 垃圾回收 (GC) 压力增大:GC 执行得越频繁,就越可能造成应用程序的短暂卡顿。
  • 内存碎片化:频繁处理和释放小块内存,可能导致内存空间中出现大量不连续的内存碎片。
  • 性能下降:内存管理本身的开销。

我们可以通过对象池 (Object Pool) 的设计模式来解决这个问题。

Object Pool 设计模式

对象池 (Object Pool) 是一种设计模式,用于管理和复用对象,以避免频繁创建和销毁对象带来的性能损耗。

与其在需要时创建、在用完时销毁,不如将可复用的对象统一管理起来,实现循环利用。 这个对象池就像一个“仓库”,预先存放一批可以重复使用的对象。当需要对象时从池中取出,使用完毕后放回到池中,而不是销毁它。

这样可以达到:

  • 复用已分配的内存:避免了大量的内存分配操作。
  • 减少垃圾回收次数:降低对主线程的干扰。

Link Pool

LinkPool 采用单向链表结构,并且依照后进先出 (LIFO) 的原则。主要是因为入池、出池都只需要对头节点进行操作,时间复杂度为 O(1),效率很高。

Link Pool 生命周期

我们接下来的执行步骤如下:

  • LinkPool 未使用
  • 移除 Link2 节点
  • 移除 Link1 节点
  • 复用 linkPool 中的节点

可以观察一下它们的链表关系以及 LinkPool 的使用情况。

初始化

day16-01.png linkPool 池是空的,什么都还没运行,没有可回收的节点。

移除 Link2

day16-02.png

通过 endTrack(sub) 判定有“尾段过期”→ 调用 clearTracking(Link2)Link2 被回收到池中。

移除 Link1

day16-03.png

通过 endTrack(sub) 再次判定有“尾段过期”→ 调用 clearTracking(Link1)Link1 被回收到池中,并排在 Link2 前面。

复用 Link1

day16-04.png

执行 link(dep, sub),这次 if (linkPool)true,走复用分支,从池中取出 Link1 进行复用。

LinkPool 代码实现

// system.ts

interface Dep {
  subs: Link | undefined
  subsTail: Link | undefined
}

interface Sub {
  deps: Link | undefined
  depsTail: Link | undefined
}

export interface Link {
  sub: Sub
  nextSub: Link
  prevSub: Link
  dep: Dep
  nextDep: Link | undefined
}

let linkPool: Link

export function link(dep, sub) {
  const currentDep = sub.depsTail
  const nextDep = currentDep === undefined ? sub.deps : currentDep.nextDep
  if (nextDep && nextDep.dep === dep) {
    sub.depsTail = nextDep
    return
  }

  let newLink: Link

  /**
   * 查看 linkPool 是否存在,如果存在,表示有可复用的节点
   */
  if (linkPool) {
    newLink = linkPool
    linkPool = linkPool.nextDep // 池指针后移
    newLink.nextDep = nextDep
    newLink.dep = dep
    newLink.sub = sub
  } else {
    /**
     * 如果 linkPool 不存在,表示没有可复用的节点,那就创建一个新节点
     */
    newLink = {
      sub,
      dep,
      nextDep,
      nextSub: undefined,
      prevSub: undefined
    }
  }

  if (dep.subsTail) {
    dep.subsTail.nextSub = newLink
    newLink.prevSub = dep.subsTail
    dep.subsTail = newLink
  } else {
    dep.subs = newLink
    dep.subsTail = newLink
  }

  if (sub.depsTail) {
    sub.depsTail.nextDep = newLink
    sub.depsTail = newLink
  } else {
    sub.deps = newLink
    sub.depsTail = newLink
  }
}

export function propagate(subs) {
  // ... (不变)
}

export function startTrack(sub) {
  // ... (不变)
}

export function endTrack(sub) {
  // ... (不变)
}

function clearTracking(link: Link) {
  while (link) {
    const { prevSub, nextSub, dep, nextDep } = link

    if (prevSub) {
      prevSub.nextSub = nextSub
      link.nextSub = undefined
    } else {
      dep.subs = nextSub
    }

    if (nextSub) {
      nextSub.prevSub = prevSub
      link.prevSub = undefined
    } else {
      dep.subsTail = prevSub
    }

    link.dep = undefined
    link.sub = undefined

    /**
     * 把不再需要的节点放回 linkPool 中,以备复用
     */
    link.nextDep = linkPool
    linkPool = link

    link = nextDep
  }
}

通过对 linkclearTracking 函数的修改,我们完成了 LinkPool 机制。这看起来是一个很小的修改,但实际上是对响应式系统底层的重要性能优化。Link 节点的生命周期从“用完即毁”变成了“循环再生”,从根本上解决了因动态依赖而产生的频繁内存分配与回收问题。


想了解更多 Vue 的相关知识,抖音、B站搜索我师父「远方os」,一起跟日安当同学。

本站提供的所有下载资源均来自互联网,仅提供学习交流使用,版权归原作者所有。如需商业使用,请联系原作者获得授权。 如您发现有涉嫌侵权的内容,请联系我们 邮箱:[email protected]