Moment.js是用于解析和格式化日期的最受欢迎的JavaScript库之一。 WhereTo使用Node.js,因此对于他们来说,使用此库是完全自然的举动。 预计服务器使用Moment.js不会出现问题。 最后,从一开始,他们就在前端使用此库来显示日期,并对其工作感到满意。 但是,库在客户端上表现良好的事实并不意味着服务器也不会出现问题。

该材料(我们今天发布的翻译)专用于解决性能问题Moment.js的故事。
项目增长和生产力下降
最近,WhereTo系统返回的飞行记录数量增长了大约十倍。 然后,我们面临着非常严重的性能下降。 事实证明,渲染周期不到100毫秒,现在需要3秒钟以上才能显示大约5,000个搜索结果。 我们的团队已开始研究。 经过几次性能分析会话后,我们注意到有99%以上的时间都花在了一个名为
createInZone
函数中。
createInZone函数大约需要3.3秒才能完成。继续对情况进行调查,我们发现此函数由Moment.js
parseZone
函数调用。 她为什么这么慢? 我们感觉Moment.js库是为常见用例设计的,因此,它将尝试以各种方式处理输入字符串。 也许您应该限制它? 阅读文档后,我们发现
parseZone
函数接受一个指定日期格式的可选参数:
moment.parseZone(input, [format])
我们要做的第一件事是尝试使用
parseZone
函数,并将日期格式的信息传递给它,但是,正如性能测试所示,它并没有导致任何结果:
$ node bench.js moment#parseZone x 22,999 ops/sec ±7.57% (68 runs sampled) moment#parseZone (with format) x 30,010 ops/sec ±8.09% (77 runs sampled)
尽管现在
parseZone
函数的工作速度更快,但是对于我们的需求而言,这种速度显然还不够。
特定于项目的优化
我们使用Moment.js解析了从提供商的API(Travelport)中检索到的日期。 我们意识到它总是以相同的格式返回数据:
"2019-12-03T14:05:00.000-07:00"
知道了这一点,我们开始理解Moment.js的内部结构,以便(如我们希望的那样)编写一个效率更高的函数,产生相同的结果。
创建一个更快的替代parseZone
首先,我们需要弄清楚Moment.js对象的外观。 这很容易理解:
> const m = moment() > console.log(m) Moment { _isAMomentObject: true, _i: '2019-12-03T14:05:00.000-07:00', _f: 'YYYY-MM-DDTHH:mm:ss.SSSSZ', _tzm: -420, _isUTC: true, _pf: { ...snip }, _locale: [object Locale], _d: 2019-12-03T14:05:00.000Z, _isValid: true, _offset: -420 }
下一步是不使用构造函数实例化Moment:
export function parseTravelportTimestamp(input: string) { const m = {}
现在看来,我们已经可以设置Moment实例的很多属性(我不详细介绍如何找到它,但是如果您查看Moment.js的源代码,您将会理解):
const FAKE = moment() const TRAVELPORT_FORMAT = 'YYYY-MM-DDTHH:mm:ss.SSSSZ' export function parseTravelportTimestamp(input: string) { const m = {}
我们工作的最后一步是找出如何解析时间戳的
offset
值。 事实证明,这始终是行中相同的位置。 结果,我们能够优化这一点:
function parseTravelportDateOffset(input: string) { const hrs = +input.slice(23, 26) const mins = +input.slice(27, 29) return hrs * 60 + (hrs < 0 ? -mins : mins) }
将所有内容放在一起后,会发生以下情况:
const FAKE = moment() const TRAVELPORT_FORMAT = 'YYYY-MM-DDTHH:mm:ss.SSSSZ' function parseTravelportDateOffset(input: string) { const hrs = +input.slice(23, 26) const mins = +input.slice(27, 29) return hrs * 60 + (hrs < 0 ? -mins : mins) } export function parseTravelportTimestamp(input: string): moment { const m = {}
性能测试
我们使用基准npm模块测试了所得解决方案的性能。 这是基准代码:
const FAKE = moment() const TRAVELPORT_FORMAT = 'YYYY-MM-DDTHH:mm:ss.SSSSZ' function parseTravelportDateOffset(input: string) { const hrs = +input.slice(23, 26) const mins = +input.slice(27, 29) return hrs * 60 + (hrs < 0 ? -mins : mins) } export function parseTravelportTimestamp(input: string): moment { const m = {}
这是我们性能研究的结果:
$ node fastMoment.bench.js moment#parseZone x 21,063 ops/sec ±7.62% (73 runs sampled) moment#parseZone (with format) x 24,620 ops/sec ±6.11% (71 runs sampled) fast#parseTravelportTimestamp x 1,357,870 ops/sec ±5.24% (79 runs sampled) Fastest is fast#parseTravelportTimestamp
事实证明,我们设法将时间戳分析速度提高了约64倍。 但是,这如何影响系统的实际运行? 这是概要分析的结果。
parseTravelportTimestamp的总执行时间少于40毫秒。结果简直令人惊讶:我们从3.3秒开始,一直到解析日期,最后不到40毫秒。
总结
当我们开始在平台上工作时,我们只需要解决大量问题。 我们只知道自己在重复自己:“先让它起作用,但您可以稍后进行优化”。
在过去的几年中,我们项目的复杂性急剧增加。 幸运的是,现在我们来到了可以进行“口头禅”第二部分-优化的地方。
图书馆解决方案帮助该项目发展到今天。 但是,我们面临着“图书馆”问题。 解决该问题后,我们了解到,通过创建自己的机制来满足需求,我们可以在消耗系统资源方面使代码“更轻松”,并为用户节省了宝贵的时间。
亲爱的读者们! 您是否遇到过与本文讨论的问题类似的问题?
