• 博客构建性能优化笔记 | 提速 3 倍


    笔者的博客基于 VitePress 搭建的,使用其自定义主题能力完成博客主题 @sugarat/theme 的搭建。

    前段时间有群友反馈说使用主题构建后耗时增加非常明显。

    前后耗时大概增加了 10 倍,过于离谱了。

    断断续续的投入差不多 1 个月的时间完成了优化,效果还是很明显。

    至此写篇文章记录&分享一下优化过程。

    先看一下优化前后的效果

    • 测试项目:笔者的博客,差不多 490 篇文章。
    • 测试机器:Mac Mini (M1, 2020)

    仅开启博客相关的样式能力

    VitePress 默认主题 优化后主题 优化前主题
    16.38s 20.56s 32.36s
    对比目标 +4.18s +15.98s

    开启拓展能力

    RSSpagefind 离线搜索

    优化后 优化后 优化前
    RSS不开启HTML生成 + 离线搜索 RSS + 离线搜索 RSS + 离线搜索
    25.70s 30.93s 50.85s
    +9.4s +14.55s +34.47s

    小结

    整体提速约 3 倍:

    • 只开启基础能力:额外耗时从 16s 缩短至 4s
    • 拓展能力耗时:额外耗时从 34s 缩短到 10 s

    问题定位

    先定位耗时的位置,再想办法进行优化。

    我们可以直接用 console.timeconsole.timeEnd 打印出耗时信息。

    console.time('flag')
    // 执行代码
    console.timeEnd('flag') // 打印出耗时
    

    主要关注有循环和外部调用的逻辑,在其前后加上打印耗时。

    简单打了几个点,就有如下的结果咯 ⏰。

    在主题入口和两个插件都有一段类似的代码逻辑,读取文件内容构造 meta 信息。

    优化方式

    异步操作文件

    读取文件内容用于提取 frontmatter 信息,生成描述,标题等内容,会用于首页渲染。

    使用 fs.promises 异步操作文件,这样可以避免阻塞进程。

    // 原
    fs.readFileSync(filePath, 'utf-8')
    // 新
    fs.promises.readFile(filePath, 'utf-8')
    

    异步创建子进程

    主要通过调用 git 指令获取文件最后的修改时间,用于展示文章的最后的修改时间。

    原来使用的 spawnSync,同样也是同步执行的方法。

    使用 spawn + Promise 替换 spawnSync,避免阻塞进程。

    // 原
    spawnSync('git', ['log', '-1', '--pretty="%ci"', url])
    
    // 新
    const child = spawn('git', ['log', '-1', '--pretty="%ai"', url])
    

    使用缓存

    在日志里可以发现,Vite 插件里 load 钩子在 vitepress build 时执行了2次。

    因此针对会重复执行的逻辑,可以添加添加一段缓存读写的逻辑,能明显降低二次执行相关逻辑的时间。

    时间的获取使用 Map 缓存文件的日期信息,在文件路径不变的情况下复用上一次获取的内容

    const cache = new Map()
    
    const cached = cache.get(url)
    if (cached) {
      return cached
    }
    

    并发执行异步操作

    如果是 await new promise 在执行的时候才创建和获取 promise 结果,提升不是特别明显。

    比如 spawn 创建子进程调用,配合 await promise,在文章数量较多时,依然会有明显的耗时。

    所以可以将文件内容和 git 时间的获取动作提前且并发执行。

    const contentPromises = files.reduce((prev, f) => {
      prev[f] = {
        contentPromise: fs.promises.readFile(f, 'utf-8'),
        datePromise: getFileBirthTime(f)
      }
      return prev
    }, {})
    

    但在测试的时候发现这样写偶尔会执行出错或提升不明显,大概是并发的执行的 Promise 和 spawn 创建子进程过多的关系。

    于是引入 p-limit 来控制并发的 promise 数。

    import os from 'node:os'
    import pLimit from 'p-limit'
    
    const limit = pLimit(+(process.env.P_LIMT_MAX || os.cpus().length))
    const metaPromise = limit(() => getArticleMeta())
    

    这里默认值使用os.cpus().length来获取 CPU 核心的数量,这样创建的子进程能充分利用上多核的能力,不然并行值调得再大,也不会有明显的提升。

    非必要第三方能力提供开关

    有些能力,可能没有用到,但是打开就是会增加额外的耗时,对文件做了不改变内容的分析与处理

    在测试中发现 RSS 生成 HTML 的逻辑非常耗时,文件内容越多,耗时越多。

    const fileContent = fs.readFileSync(file, 'utf-8')
    const { createMarkdownRenderer } = await import('vitepress')
    const mdRender = await createMarkdownRenderer()
    const html = mdRender.render(fileContent)
    

    vitepress 内置使用的 markdown-it ,并且内置了许多的插件,html 作为 RSS 内容的组成也不是必要的部分,因此可以做成可选的能力,交由用户选择是否开启,同时将生成的方式也做成可配置的,用户可以传入更加精简的生成方法。

    另一个是 markdown 图表的渲染,主题内置的 mermaid 相关插件,发现打开即使页面里没有使用,也会增加额外的耗时,且会增加非常的多。

    因此将这个也弄成默认关闭,由用户自己选择是否开启,深度优化需要修改对应插件的源码,还没来得及研究这个计划后续再做。

    最后

    个人觉得代码应该还有优化空间,下来再探索一下,攒一波有重大突破再来分享分享。

    博客本身的优化,之前也发文章分享过来,感兴趣的可以看看:博客性能优化笔记

    没错:已经拉满了!

  • 相关阅读:
    ELK整合Filebeat监控nginx日志
    caffe搭建squeezenet网络的整套工程
    redis相关面试题
    隐私计算 FATE - 多分类神经网络算法测试
    Java架构师缓存通用设计方案
    Flink中的批和流
    遗留代码处理技巧与案例演示
    Spring Security入门
    const关键字
    【“在路上”疫情信息检测】——项目页面搭建
  • 原文地址:https://www.cnblogs.com/roseAT/p/18244842