Reqable项目日志——JSON语法高亮性能200倍提升

好久没有更新Reqable的项目日志了,今天趁着版本发出去的一点点空闲时间,写一篇文章记录下前几天的一个重要性优化。

背景介绍

Reqable中JSON语法高亮的性能问题由来已久了,早在2023年9月份的时候,就有用户反馈JSON单个字段超过8055后无法正常高亮,详见 Issue #195,当时评估后不太好解决就挂起了。前段时间,又有用户反馈不到2M的JSON文件高亮渲染性能很差,详见 Issue #1421。这次,我决定把这个问题再次好好捋一捋。

语法高亮原理

先说说Reqable中是如何实现文本语法高亮的。

早期我在Github上面找到一个非常不错的开源项目highlight.dart,这个项目是基于highlight.js的原理实现的,一个是版本太低,一个是实现逻辑有些问题,因此存在不少Bug。

放弃之后,我们决定参考highlight.dart的思路,将highlight.js用Dart语言严格地完完整整地翻译了一遍(已经开源re-highlight),才总算实现了语法高亮功能。

接下来,我们说说highlight.js这个项目,实现主要分为三个部分语言模式处理器样式定义语言模式定义了各种语言的语法,例如关键词、变量定义、函数定义等等语法规则,大量利用正则表达式进行语法定义和约束。处理器负责根据指定的语言模式对输入文本进行匹配处理,输出这段文本中哪部分是关键词、哪部分是变量、哪部分是函数等等,打上标签。如果在未提前指定语言的情况下,还可以对所有语言进行处理,并输出每个语言的置信度,自动做一个语言检测。样式定义则是定义了大量的主题样式,比如字体颜色、背景色、字体粗细等等,根据前面处理器输出结果用相应的样式进行渲染,便可以看到语法高亮的效果了。

相信看到这里,大家都能猜测到性能瓶颈在哪里了。没错,就是正则表达式。但是正则表达式效率低归低,但是JSON单个字段超过8055后无法正常高亮又是怎么回事呢?

Dart StackOverflow

JSON单个字段超过8055后无法正常高亮这个问题,在用户没有提出来之前,在2023年6月份的时候我们就已经发现了。在Release模式运行时,有些数据JSON高亮会失败,但是在Debug模式下同样的数据没有任何问题。执行语法高亮的逻辑是在一个单独的Isolate中执行的,在Release模式下没有任何输出,仿佛没有执行,一度让我感到困扰。

直到我在Dart仓库下发现这个 Dart compile: StackOverflow when running RegExp.matchAsPrefix ,Dart语言为了性能,AoT下Stack设计得比较小,容易触发StackOverflow。这个问题引来了highlight.js和Dart两边维护者关于正则表达式写法性能的相关讨论。重写正则表达式不可能,重新调参编译Dart VM也挺麻烦,当时又想要不自己重写一套JSON语法高亮解析器算了,因为其他事情要处理,就搁置了。

解决问题

现在回过头来,看这个问题,比较可行的方案就是单独给JSON写一套语法高亮解析器。输入字符串从头到尾扫描一遍,也就是O(n)的算法复杂度,肯定是不会有性能瓶颈的,JSON节点树深浅,即使是用递归也不会爆栈。问题在于,如何优雅地接入到现在的项目中?能否利用highlight.js本身的机制做到低成本接入?

再次回过头来看highlight.js的代码,直到看到下面这段,心中大喜:

js 复制代码
/** @type {BeforeHighlightContext} */
const context = {
  code,
  language: languageName
};
// the plugin can change the desired language or the code to be highlighted
// just be changing the object it was passed
fire("before:highlight", context);

// a before plugin can usurp the result completely by providing it's own
// in which case we don't even need to call highlight
const result = context.result
  ? context.result
  : _highlight(context.language, context.code, ignoreIllegals);

result.code = context.code;
// the plugin can change anything in result to suite it
fire("after:highlight", result);

原来highlight.js中已经提供了插件API,支持前置处理和后置处理,可以替换掉默认的高亮处理逻辑。所以,我只需要提供一个自定义的JSON解析插件然后注册进去即可,其他任何修改都不需要!

接下来,就是如何在插件里面实现JSON解析逻辑了,像JSON这种严格语法的解析器其实很好写的。不过需要注意的是,和常规的JSON解析不同,我们需要保留全部的空格、换行符、符号等等字符,而不是输出一个Map结构。另外,输出需要按照highlight.js定义的格式,在解析器中还需要做一些特殊处理。终于在Github Copilot的加持下,写完了这个插件,大约300行代码,详见:github.com/reqable/re-...

性能测试

最后,我们测试下性能,用Issue #195中用户提供的JSON数据(大约1.6M)来实测跑下。优化前的版本在我的Apple M2上跑一遍语法高亮需要30s,优化后跑了一遍我惊呆了,只要150ms,差不多200倍的提升!

欢迎各位阅读!也欢迎大家来下载和体验 Reqable - 新一代API生产力工具,也欢迎与我一起交流更多Flutter的开发经验和心得!

相关推荐
大波V57 分钟前
vue3 使用docxtemplater 动态生成docx
前端·javascript·vue.js
1024小神7 分钟前
网页注入js代码实现获取请求的url和请求体内容,并获取响应体内容
前端·javascript
Fuzzyface8 分钟前
SPA是如何通过js不刷新页面但是更新浏览器的url的?
前端·javascript
simple丶8 分钟前
前端工程化:框架基础搭建
前端
用户25871419326322 分钟前
Vue3使用多线程处理文件分片任务
前端
不懂装懂的不懂24 分钟前
【vue3】中断请求、取消请求
前端·javascript·vue.js
鱼樱前端29 分钟前
React18+pnpm+Ts+React-Router v6从0-1搭建后台系统
前端·javascript·react.js
Epicurus30 分钟前
ES6箭头函数
前端
掘金0131 分钟前
手把手教你使用 FLV.js 在 Vue 项目中播放 FLV 视频
前端
前端没钱31 分钟前
vue3怎么和大模型交互?
前端