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

· 浏览次数 : 0

小编点评

本文将分享笔者在搭建 VitePress 博客时,如何通过一系列优化措施,成功地将博客的主题构建时间从 1 个月缩短至 3 小时左右,并在此过程中解决了一些常见问题。 一、背景与问题 笔者在使用 VitePress 搭建博客时,发现使用自定义主题搭建完成后,耗时明显增加,从原本的 20 秒左右延长至 10 分钟左右。这导致了作者在完成博客搭建的过程中遇到了较大的困扰。 二、问题定位 为了找出耗时过长的原因,我们首先使用了 console.time 和 console.timeEnd 来打印出各个步骤的耗时信息。通过分析,我们发现在主题入口和两个插件中有一段相似的代码逻辑,该逻辑用于读取文件内容并构造 meta 信息。这就是导致耗时过长的主要原因之一。 三、优化方案 针对上述问题,我们采取了以下优化方案: 1. 异步操作文件读取:使用 fs.promises 替代原生的 fs.readFileSync 函数,以避免阻塞进程。 2. 异步创建子进程:使用 spawn 和 Promise 替换原生的 spawnSync 函数,从而避免阻塞进程。 3. 缓存文件日期信息:使用 Map 对象缓存文件的修改时间,确保在文件路径不变的情况下能够复用上一次获取的内容。 4. 并发执行异步操作:为了避免频繁地创建新的 Promise 和 spawn 子进程,我们引入了 p-limit 来限制并发的 promise 数量。 四、实施与测试 在实际操作中,我们对上述方案进行了逐一尝试和验证。经过测试,我们发现大部分的耗时优化措施都可以明显提升性能,其中最快的一个优化措施是将原本同步执行的文件读取操作改为异步操作,这一改动使得单个文章的文章列表加载时间从原本的几十秒缩短至几秒钟。 五、总结与展望 通过一系列的优化措施,我们将原本需要一个月左右的博客搭建时间缩短至大约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 相关插件,发现打开即使页面里没有使用,也会增加额外的耗时,且会增加非常的多。

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

最后

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

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

没错:已经拉满了!

与博客构建性能优化笔记 | 提速 3 倍相似的内容:

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

笔者的博客基于 VitePress 搭建的,使用其自定义主题能力完成博客主题 @sugarat/theme 的搭建。 前段时间有群友反馈说使用主题构建后耗时增加非常明显。 前后耗时大概增加了 10 倍,过于离谱了。 断断续续的投入差不多 1 个月的时间完成了优化,效果还是很明显。 至此写篇文章记录&

Node.js vs. Spring Boot:Hello World 性能对决,谁更快一点?

摘要:本文由葡萄城技术团队于博客园发布。转载请注明出处:葡萄城官网,葡萄城为开发者提供专业的开发工具、解决方案和服务,赋能开发者。 前言: Spring Boot 在 Java 生态中备受欢迎,它是一款基于 Java 构建的轻量级服务端框架,主要用于 Web 服务。Spring Boot 的应用使得

云小课|基于华为云WAF的日志运维分析,构筑设备安全的城墙

阅识风云是华为云信息大咖,擅长将复杂信息多元化呈现,其出品的一张图(云图说)、深入浅出的博文(云小课)或短视频(云视厅)总有一款能让您快速上手华为云。更多精彩内容请单击此处。 摘要:云日志服务用于收集来自主机和云服务的日志数据,通过海量日志数据的分析与处理,可以将云服务和应用程序的可用性和性能最大化

【.NET项目分享】免费开源的静态博客生成工具EasyBlog,5分钟拥有自己的博客

EasyBlog 说明 本博客系统通过构建工具生成纯静态的博客网站,借助GitHub Pages,你可以在5分钟内免费拥有个人博客。 它具有以下特点 生成纯静态网站,访问速度极快 使用markdown格式来编写博客内容 基于git代码管理来存储你的博客 使用CI工具来自动化部署你的博客站点 效果展示

能快速构建和定制网络拓扑图的WPF开源项目-NodeNetwork

大家好,我是沙漠尽头的狼,今天介绍一个WPF开源项目-NodeNetwork,它可以帮助我们快速构建和定制网络拓扑图。 一、前言 在现代软件开发中,数据可视化和可交互性越来越受到关注。为了实现这一点,通常需要使用各种图表、表格、网络拓扑图等控件。然而,对于某些特殊的场景,这些控件可能无法满足需求,此

构建基于深度学习神经网络协同过滤模型(NCF)的视频推荐系统(Python3.10/Tensorflow2.11)

毋庸讳言,和传统架构(BS开发/CS开发)相比,人工智能技术确实有一定的基础门槛,它注定不是大众化,普适化的东西。但也不能否认,人工智能技术也具备像传统架构一样“套路化”的流程,也就是说,我们大可不必自己手动构建基于神经网络的机器学习系统,直接使用深度学习框架反而更加简单,深度学习可以帮助我们自动地从原始数据中提取特征,不需要手动选择和提取特征。

使用Docker buildx 为 .NET 构建多平台镜像

.NET 团队有一篇博客 改进多平台容器支持, 详细介绍了.NET 7 以上的平台可以轻松的使用Docker buildx 工具构建多平台的镜像。 buildx 是 Docker 官方提供的一个构建工具,它可以帮助用户快速、高效地构建 Docker 镜像,并支持多种平台的构建。使用 buildx,用

物以类聚人以群分,通过GensimLda文本聚类构建人工智能个性化推荐系统(Python3.10)

众所周知,个性化推荐系统能够根据用户的兴趣、偏好等信息向用户推荐相关内容,使得用户更感兴趣,从而提升用户体验,提高用户粘度,之前我们曾经使用协同过滤算法构建过个性化推荐系统,但基于显式反馈的算法就会有一定的局限性,本次我们使用无监督的Lda文本聚类方式来构建文本的个性化推荐系统。 推荐算法:协同过滤

人工智能,丹青圣手,全平台(原生/Docker)构建Stable-Diffusion-Webui的AI绘画库教程(Python3.10/Pytorch1.13.0)

世间无限丹青手,遇上AI画不成。最近一段时间,可能所有人类画师都得发出一句“既生瑜,何生亮”的感叹,因为AI 绘画通用算法Stable Diffusion已然超神,无需美术基础,也不用经年累月的刻苦练习,只需要一台电脑,人人都可以是丹青圣手。 本次我们全平台构建基于Stable-Diffusion算

红袖添香,绝代妖娆,Ruby语言基础入门教程之Ruby3基础语法,第一次亲密接触EP01

书接上回,前一篇我们在全平台构建好了Ruby3的开发环境,现在,可以和Ruby3第一次亲密接触了。 Ruby是一门在面向对象层面无所不用其极的解释型编程语言。 我们可以把编写Ruby代码看作是一场行为上的艺术,编码就像跳舞一样,Ruby的每一步都很优雅,几乎没有一步是多余的。 第一行代码 进入系统的