序言
我为什么要写这篇文章? 这可能是因为前段时间看到了一篇文章关于对使用rust
,go
…语言重构前端基建的态度,作者从几个纬度以及该如何做表达了他的看法,对此我认为是十分有意义的。无论是 riir
还是什么其他的,javaScript
对于熟悉NodeJs
或者front-end
的开发人员都是极其友好的,不需要面对未知的segfault
。
使用Profile评估你的代码
在这我从我的实际项目作为出发点记录下代码优化的差异具体的改动在tar-mini
。这篇blog会很长我可能有时间会写下篇关于流的一些优化。 这篇我只会详细地记录encode
的优化。
我们可以简单的编写个测试代码。
// encode-test.js
import { F_MODE, TypeFlag, encode } from 'tar-mini'
const filename = 'nonzzz.txt'
const content = new Uint8Array([49, 49, 49, 49])
for (let i = 0; i < 1000; i++) {
encode({
name: filename,
size: content.length,
uid: 0,
gid: 0,
mtime: Math.floor(Date.now() / 1000),
typeflag: TypeFlag.AREG_TYPE,
linkname: '',
devmajor: 0,
devminor: 0,
mode: F_MODE,
uname: 'nonzzz',
gname: 'admin'
})
}
node --prof encode-test.js
# 这里会生成一个日志然后我们调用
node --prof-process 你生成的日志 > encode.txt
然后查看生成的encode.txt
我们能发现在[JavaScript] 这一段我们有大量的TextEncode
的开销。 那么这显然是不太对的。因为一开始我们的代码定义如下
const enc = new TextEncode()
const encodeString = enc.encode.bind(enc)
function encode() {
encodeString()
// ... multiple call encodeString
}
既然这里的开销和自己的认知不符那么我们就可以借助搜索引擎去检索相关信息不出意外我们得到了关于对TextEncode
的描述。issue 既然每次encodeString
都会重复创建一块新的Uint8Array
那么我们能不能只分配一次呢?答案是肯定的我们既然是编写一个tar header encode
那我们可以根据最长的字段来提前分配一段内存。 因此我们只需要分配256 bytename(100) + prefix(155)
.
const enc = new TextEncode()
function createUT8Encode() {
const alloc = new Uint8Array(256)
return function encodeString() {
const { written } = enc.encodeInto(s, alloc)
return alloc.subarray(0, written)
}
}
这样我们就能避免重复创建大量的uint8Array
提升性能。然后我们再看看还有什么地方可以优化。我们注意到了chksum
函数的开销是有异常的。
function chksum(b: Uint8Array) {
return b.subarray(0, 512).reduce((acc, cur, i) => {
if (i >= 148 && i < 156) {
return acc + Magic.WHITE_SPACE // WHITE_SPACE mean ASCII 32
}
return acc + cur
}, 0)
}
我们在这个函数做了无意义的判断开销。同时根据定义我们不需要这个判断的开销只需要
function chksum(b: Uint8Array) {
// 148 ~ 156
let sum = Magic.WHITE_SPACE * 8 // 根据定义148 ~ 156 中间是有 white_space 填充的
for (let i = 0; i < 148; i++) {
sum += b[i]
}
for (let i = 156; i < 512; i++) {
sum += b[i]
}
return sum
}
在此观察发现剩余的开销是发生在encodeOctal
上 根据日志我们发现stringRepeat
的速度并不理想在跑过Js perf
得知对比
const s = '7777777'
const fastStr = s.slice(0, 3) // fast
const slowStr = '7'.repeat(3) // slow
// fastStr的跑分是slowStr的两倍。
接着我们重构我们的encodeOctal
为
// past
function encodeOctal(b: number, fixed?: number) {
const o = b.toString(8)
if (fixed) {
if (o.length <= fixed) {
const fill = '0'.repeat(fixed - o.length)
return fill + o + ' '
}
return '7'.repeat(fixed) + ' '
}
return o
}
// after
function encodeOctal(b: number, fixed: number) {
const o = b.toString(8)
if (o.length > fixed) { return Magic.SEVENS.slice(0, fixed) + ' ' }
return Magic.ZEROS.slice(0, fixed - o.length) + o + ' '
}
然后分别对比优化前后的性能差距
基于这个图表做了上述的优化我们提升了4倍多的性能。而这些性能的获取相比riir
他是廉价的。我们只需要使用prof
和bench
就能轻易的得到一些提升。