性能优化:500w字符编码需要多少时间?

背景

默默干鸿蒙的某一天,我们突然接到客户报告的一个缺陷:鸿蒙页面在操作过程中出现卡顿。作为跨端框架的提供者,我们负责鸿蒙底座的构建,而业务代码则由客户自行开发。经排查,问题源于鸿蒙的JSVM模块------一个提供完整JS引擎能力的核心模块,负责创建和销毁引擎、执行JS代码以及JS与C++的交互等功能。难以想象,一段业务传递的JS代码在该模块中运行时间竟长达1.6秒?经过抽丝剥茧,我们发现客户将一段对500万字符进行编码的JS脚本放入了鸿蒙的JSVM中运行。

最后发现,客户将一串500万字符的字符串进行了两次编码拼接操作,全程使用 + 运算符进行字符串连接。不了解业务为什么这么做,但需要跟他们说明原因并协助其修改。

目前,虽然这段 500w 字符编码的 JS 脚本,在 chrome 上运行不过500多ms,但在鸿蒙的 jsvm 中运行将是1.6s左右。针对客户对鸿蒙性能的质疑,也只能后续再进行更深入的技术分析了。

优化目标

既然要进行优化,那么要优化到什么程度才算合理呢?

在24年,Chrome 的性能面板有一项重大改版,INP 取代 FID 成为新的 Core Web Vitals

通常情况下,JavaScript是驱动页面交互的主要力量,尽管部分控件(如复选框、单选按钮以及纯CSS控件)可以不依赖于JavaScript。因此,JavaScript的执行效率会直接影响INP指标。

按照上述互动性响应能力评估,js 脚本的运行时间应控制在200毫秒以内,以提升用户体验。因此,我们可以将200毫秒作为本次性能优化的目标。下面就是分析有哪些可优化的手段了。

字符拼接方式

字符编码过程中必不可少的就是字符拼接。字符拼接有以下几种方式

  • + 运算符
  • 使用 concat() 方法
  • 存放数组,最后使用数组 join() 方法

根据以上三种方式,分别对1千、1万、10万、100万、500万的字符在chrome中独立进行拼接耗时检测

javascript 复制代码
const str01 = new Array(1000).fill('e').join('');
const str1 = new Array(10000).fill('e').join('');
const str10 = new Array(100000).fill('e').join('');
const str100 = new Array(1000000).fill('e').join('');
const str500 = new Array(5000000).fill('e').join('');

function addStr(str,num) {
    console.time('+ ' + num);
  let res = '';
  for (let i = 0; i < str.length; i++) {
    res += str.charCodeAt(i);
  }
  console.timeEnd('+ ' + num);
}

  function concatStr(str, num) {
    console.time('concat' + num);
    let res = '';
    for (let i = 0; i < str.length; i++) {
      res = res.concat(str.charCodeAt(i));
    }
    console.timeEnd('concat' + num);
  }

  function arrStr(str, num) {
    console.time('arr' + num);
    let res = '';
    let arr = []
    for (let i = 0; i < str.length; i++) {
        arr.push(str.charCodeAt(i));
    }
    res = arr.join('');
    console.timeEnd('arr' + num);
  }

以上的数据受设备性能、浏览器及其当下的状态影响。10w字符以下的处理三者差异不大,10w以上差异明显

客户现有的编码代码(两种类型编码:base64encode(utf16to8(p)) )中使用了 += 的方式对编码后的字符进行了字符的拼接,导致当对500w字符进行编码时,总耗时有500ms左右。将拼接方式换成Array.push()方式后,总编码耗时平均在200ms 左右。

数组转字符方式

数组转字符也不用数据测试,join() 是在无特殊情况下,全量数组元素转字符最高效的方式。但是,当独立对一个500w个字符类型元素的数组进行 join 时,平均耗时仍需40ms 左右。

以当前编码为例,需要将字符串先转换成utf-8,然后再进行编码,此处会对500w的字符进行2次遍历

在上述流程中,可以去除第一步utf16to8功能数组转换字符串 步骤 能够再降低一部分的耗时,由最初的500ms 左右优化到150ms 左右。

字符取值方式

做完以上的处理其实已经优化的差不多了,但是500w字符120ms 的耗时,还是想着会不会有更优解?那么从字符取值方式上验证下看看?

字符取值有以下几种方式

  • 使用方括号([])语法
  • 使用 charAt() 方法
  • 使用 charCodeAt() 方法(然后转换为字符)
  • 使用 codePointAt() 方法(然后转换为字符)
  • 使用 at() 方法

简单做了下500w字符的取值耗时运行代码

javascript 复制代码
const str = new Array(5000000).fill('e').join('');
console.time('index access');
for (let i = 0; i < str.length; i++) {
  const char = str[i];
}
console.timeEnd('index access');
console.time('charAt method');
for (let i = 0; i < str.length; i++) {
  const char = str.charAt(i);
}
console.timeEnd('charAt method');

console.time('charCodeAt method');
for (let i = 0; i < str.length; i++) {
  const char = String.fromCharCode(str.charCodeAt(i));
}
console.timeEnd('charCodeAt method');

console.time('codePointAt method');
for (let i = 0; i < str.length; i++) {
  const char = String.fromCharCode(str.codePointAt(i));
}
console.timeEnd('codePointAt method');
console.time('at method');
for (let i = 0; i < str.length; i++) {
  const char = str.at(i);
}
console.timeEnd('at method');

主要在node和chrome 上运行了下数据,3次取平均值

node(ms) chrome(ms)
[] 6.446 4.593
charAt 6.076 6.596
charCodeAt 6.558 2.701
codePointAt 12.658 3.503
at 27.402 22.994

根据数据表示,该项没有特别大的变化,一般也不会使用at的方式去取值,而其他的方式针对500w数据量字符的优化也有限,最多不过能提升4ms,没有特别大的必要。

结果

优化后的代码在Chrome上的运行时间约为150毫秒,在鸿蒙上约为500毫秒。客户后来发现问题是由于开发人员在适配鸿蒙时错误地将多余的字符一起编码,实际上每次编码的字符最多只有100万。

总结

尽管问题最终被证明是一个乌龙,但通过这个过程,代码性能得到了优化,对鸿蒙和Chrome都有所改进。在JavaScript中进行性能优化的重要性不言而喻,特别是在处理大量数据时,选择合适的字符串操作和编码方法对性能有显著影响。针对大量字符编码时:

  • 避免在循环中使用字符串拼接,使用Array.push()和join()方法代替。
  • 对于大量字符的编码,考虑使用更高效的编码算法和数据结构。
相关推荐
艾小逗1 小时前
vue3中的effectScope有什么作用,如何使用?如何自动清理
前端·javascript·vue.js
小小小小宇4 小时前
手写 zustand
前端
Hamm4 小时前
用装饰器和ElementPlus,我们在NPM发布了这个好用的表格组件包
前端·vue.js·typescript
小小小小宇5 小时前
前端国际化看这一篇就够了
前端
大G哥5 小时前
PHP标签+注释+html混写+变量
android·开发语言·前端·html·php
whoarethenext5 小时前
html初识
前端·html
小小小小宇6 小时前
一个功能相对完善的前端 Emoji
前端
m0_627827526 小时前
vue中 vue.config.js反向代理
前端
Java&Develop6 小时前
onloyoffice历史版本功能实现,版本恢复功能,编辑器功能实现 springboot+vue2
前端·spring boot·编辑器
白泽talk6 小时前
2个小时1w字| React & Golang 全栈微服务实战
前端·后端·微服务