Cost of parsing JSON

JavaScript

在 JavaScript 中,直接定义一个对象(Object),性能上远不如定义一个 JSON.parse() 的表达式。具体来说,下面的两行,JSON.parse 的表达式会有更好的性能表现:

const slow = { foo: 42, bar: 1337 };
const fast = JSON.parse('{"foo":42,"bar":1337}');

同样的效果,但是在 JavaScript 引擎中的表现却差别很大。根据这里给出的测试数据,JSON.parse 的速度是直接写对象速度的 1.7 倍。而且这不仅仅只是 V8 表现上的不同,在各类 JavaScript 引擎上都有类似的表现,性能差异均非常明显(JavaScriptCore 的性能差异可以到两倍)。

这里差异的主要原因在于,引擎在解析时候算法复杂度有着巨大的差异。简单来说,JSON 的数据结构是非常简单且固定的,因而在解析的时候可以有更好的表现。这种简单体现在以下几个方面:

  1. JSON 的数据支持类型不多,只有字符串,数组,数字,NULL,对象这几种;相比之下,JavaScript 中一个对象的支持类型非常的复杂,情况更多;
  2. 从抽象语法树(AST)的角度看,JSON.parse 的情况比单纯写一个 JavaScript 对象要简单的多。对于前者来说,就是一个 CallExpression 和一个 StringLiteral;而对于一个 JavaScript 对象来说,涉及到大量的 ObjectExpression,当中可能还包含 StringLiteral,NumericLiteral,Identifier 等等;
  3. JSON 的解析是上下文无关的;而 JavaScript 对象的解析却需要结合当前的上下文(context)来确定;

举一个例子来说明:假设有这样一个 JavaScript 代码片段:

const x = 1;
const y = ({ x }

这里的 x 代表什么含义,其实有非常多的可能性,比如:

  • const y = ({ x }),此时 x 的值和上下文中的 x 变量是相关的,定义是一个 JavaScript 对象;
  • const y = ({ x } = { x: 2 }),此时 x 和上下文是相关的,但定义的是一个赋值语句,而不是对象(根据语法,对 const 二次赋值导致语法错误);
  • const y = ({ x }) => x;,此时 x 的值和上面的 x 无关,是一个函数的参数;

换句话说,当 JavaScript 引擎在解析一个 JavaScript 对象的时候,需要考虑很多的可能性,在解析的过程中很可能无法确定当前的类型,甚至连语法是否正确也不能确定。但反观 JSON,定义就简单的多,在解析的当下,引擎就可以很清楚的知道当前的内容是一个数组,还是一个对象,亦或是有语法错误。

除了上述提到的性能比较数据之外,这里还有一份针对 Redux 应用的优化分析。数据显示,使用 JSON.parse 调用之后 TTI (Time To Interactive) 时间缩短了 0.74s (18%)。考虑到整个改动是非常“简单”的,这一性能提升显得非常客观。

这里之所以说改动是非常“简单”的,是因为整个优化思路非常的明确,完全可以通过对应的工具在编译时完成。目前开源社区已经提供了各类相关的工具,可以直接使用,列举一些如下: