How to improve JSON.stringify () performance?

1. familiarJSON.stringify()

In the browser or server, JSON.stringify()it is our very commonly used method:

  • The JSON object stored in localStorage;
  • JSON body POST request;
  • Processing JSON body form data response;
  • Even under certain conditions, we will use it to achieve a simple deep copy;
  • ……

In some performance-sensitive applications (such as server handle a large number of concurrent), or when the face of a large number of stringify operation, we will hope to better its performance, faster. It also spawned a number of optimization programs stringify / library, it is the performance comparison thereof with FIG green original method:

6476654-48ed602e26326336
image

Native green part of the JSON.stringify()visible compared to the performance of these libraries should be much lower. What, then, to enhance the technical principles behind the sharp performance is it?

2. Than stringifyFasterstringify

Because JavaScript is very dynamic language, so for a variable of type Object, key, key, key types it contains can only be determined at run time. Therefore, the implementation JSON.stringify()will have a lot of work to do when. In the case of ignorance, we want to significantly optimize apparently powerless.

So if we know that in the Object keys, key information it - that is, to know its structure information, which will help it?

Look at an example:

The following Object,

const obj = {
    name: 'alienzhou',
    status: 6,
    working: true
};

We use it JSON.stringify()to obtain results

JSON.stringify(obj);
// {"name":"alienzhou","status":6,"working":true}

Now if we know that objthe structure is fixed:

  • Keys unchanged
  • Certain types of keys

Well actually, I can create a "customized" method of stringify

function myStringify(o) {
    return (
        '{"name":"'
        + o.name
        + '","status":'
        + o.status
        + ',"isWorking":'
        + o.working
        + '}'
    );
}

Check out our myStringifyoutput method:

myStringify({
    name: 'alienzhou',
    status: 6,
    working: true
});
// {"name":"alienzhou","status":6,"isWorking":true}

myStringify({
    name: 'mengshou',
    status: 3,
    working: false
});
// {"name":"mengshou","status":3,"isWorking":false}

Get the right result, but only uses the type conversion and string concatenation, so "customized" approach allows "stringify" faster.

Overall, we compared how to get stringifya faster stringifyway to do that?

  1. Structural information required to determine the object;
  2. According to its configuration information to create an object that such structure "Custom" of the stringifymethod, which is generated inside the actual results by the string concatenation;
  3. Finally, use the "customized" approach to stringify objects can be.

This is also the most stringify accelerate routine libraries into code that is similar to:

import faster from 'some_library_faster_stringify';

// 1. 通过相应规则,定义你的对象结构
const theObjectScheme = {
    // ……
};

// 2. 根据结构,得到一个定制化的方法
const stringify = faster(theObjectScheme);

// 3. 调用方法,快速 stringify
const target = {
    // ……
};
stringify(target);

3. How to generate a "customized" approach

According to the above analysis, the core functionality that according to its information structure, the class create "customized" stringify method of object, its interior is actually simple property access to the string concatenation.

In order to understand the specific implementation, following me to achieve slightly different on the two open source libraries as an example to briefly explain.

3.1. fast-json-stringify

6476654-f128c9783fda67cb
image

The figure is in accordance with FAST-JSON-the stringify Benchmark results provided, sort out performance comparison.

6476654-2b4a7c3c1772c102
image

We can see, have 2-5 times the performance in most scenarios.

3.1.1. Scheme defined way

fast-json-stringify using JSON Schema Validation to define the data format (JSON) object. Scheme which itself is defined by the structure of JSON format, such as object

{
    name: 'alienzhou',
    status: 6,
    working: true
}

Corresponding scheme is:

{
    title: 'Example Schema',
    type: 'object',
    properties: {
        name: {
            type: 'string'
        },
        status: {
            type: 'integer'
        },
        working: {
            type: 'boolean'
        }
    }
}

Its rich scheme defined rules, refer to the specific use Ajv this check JSON library.

3.1.2 Method of generating stringify

fast-json-stringify just defined based scheme, the actual splicing generates the function code string, and then use the Function constructor dynamically generated corresponding stringify functions at runtime.

In the code generation, first of all it will inject pre-defined method for all kinds of tools, different portions of the scheme is the same:

var code = `
    'use strict'
  `

  code += `
    ${$asString.toString()}
    ${$asStringNullable.toString()}
    ${$asStringSmall.toString()}
    ${$asNumber.toString()}
    ${$asNumberNullable.toString()}
    ${$asIntegerNullable.toString()}
    ${$asNull.toString()}
    ${$asBoolean.toString()}
    ${$asBooleanNullable.toString()}
  `

Next, the specific code is generated based on the specific contents of the function stringify defined scheme. Generated relatively simple way: by walking scheme.

Traversal scheme, according to a defined type, inserted into the corresponding utility function to convert code corresponding to the key value. For example, in the above example namethis property:

var accessor = key.indexOf('[') === 0 ? sanitizeKey(key) : `['${sanitizeKey(key)}']`
switch (type) {
    case 'null':
        code += `
            json += $asNull()
        `
        break
    case 'string':
        code += nullable ? `json += obj${accessor} === null ? null : $asString(obj${accessor})` : `json += $asString(obj${accessor})`
        break
    case 'integer':
        code += nullable ? `json += obj${accessor} === null ? null : $asInteger(obj${accessor})` : `json += $asInteger(obj${accessor})`
        break
    ……

The above code codestored in the last variable is the code string generated function body. Since the definition scheme, nameas the stringtype, and does not, it will be empty codeadding a code string as follows:

"json += $asString(obj['name'])"

Since the need to handle complex situations arrays, and the like with an object, the actual code number is omitted.

Then, the resulting complete codestring is as follows:

function $asString(str) {
    // ……
}
function $asStringNullable(str) {
    // ……
}
function $asStringSmall(str) {
    // ……
}
function $asNumber(i) {
    // ……
}
function $asNumberNullable(i) {
    // ……
}
/* 以上是一系列通用的键值转换方法 */

/* $main 就是 stringify 的主体函数 */
function $main(input) {
    var obj = typeof input.toJSON === 'function'
        ? input.toJSON()
        : input

    var json = '{'
    var addComma = false
    if (obj['name'] !== undefined) {
        if (addComma) {
            json += ','
        }
        addComma = true
        json += '"name":'
        json += $asString(obj['name'])
    }

    // …… 其他属性(status、working)的拼接

    json += '}'
    return json
}

return $main

Finally, the codestring passed in Function constructor to create the appropriate stringify function.

// dependencies 主要用于处理包含 anyOf 与 if 语法的情况
dependenciesName.push(code)
return (Function.apply(null, dependenciesName).apply(null, dependencies))

3.2. slow-json-stringify

6476654-ace547e594472324
image

slow-json-stringify Although the name is "slow", but in fact is a "fast" of stringify library (named very naughty).

The slowest stringifier in the known universe. Just kidding, it's the fastest (:

Its implementation is more lightweight than the fast-json-stringify mentioned earlier, the idea is very clever. At the same time it efficiency faster than fast-json-stringify in many scenes .

6476654-5865798c0046fd64
image
6476654-5482575e02b4a5ab
image

3.2.1. Scheme defined way

scheme defined slow-json-stringify more natural and simple, the main key is to replace the type described. Or the case of the above object, scheme becomes

{
    name: 'string',
    status: 'number',
    working: 'boolean'
}

Really very intuitive.

3.2.2 Method of generating stringify

I do not know if you noticed

// scheme
{
    name: 'string',
    status: 'number',
    working: 'boolean'
}

// 目标对象
{
    name: 'alienzhou',
    status: 6,
    working: true
}

Construction scheme and the original object is not like?

The trick of this scheme is that, after this definition, we can put the scheme JSON.stringifya bit, then "deduction" all types of values, and finally waiting for us is to fill the actual value of the scheme directly to the corresponding type declaration.

Specifically, how does it work?

First, the scheme can be directly invoked JSON.stringify()to generate a base template, while borrow JSON.stringify()the second argument as the access path traversal attribute collected:

let map = {};
const str = JSON.stringify(schema, (prop, value) => {
    const isArray = Array.isArray(value);
    if (typeof value !== 'object' || isArray) {
        if (isArray) {
            const current = value[0];
            arrais.set(prop, current);
        }

        _validator(value);

        map[prop] = _deepPath(schema, prop);
        props += `"${prop}"|`;
    }
    return value;
});

At this time, mapin the access path to collect all the attributes. Simultaneously generated propscan be spliced to match the corresponding type of character is also a regular expression, such as our example of regular expression /name|status|working"(string|number|boolean|undef)"|\\[(.*?)\\]/.

Then, according to the regular expression to match the order of these attributes, the attribute type replace the string, the string into uniform footprint "__par__", based on "__par__"splitting the string:

const queue = [];
const chunks = str
    .replace(regex, (type) => {
      switch (type) {
        case '"string"':
        case '"undefined"':
          return '"__par__"';
        case '"number"':
        case '"boolean"':
        case '["array-simple"]':
        case '[null]':
          return '__par__';
        default:
          const prop = type.match(/(?<=\").+?(?=\")/)[0];
          queue.push(prop);
          return type;
      }
    })
    .split('__par__');

In this way you will get chunksand the propstwo arrays. chunksContains a JSON string is split. In the example, the two arrays are as follows

// chunks
[
    '{"name":"',
    '","status":"',
    '","working":"',
    '"}'
]

// props
[
    'name',
    'status',
    'working'
]

Finally, since the map stored in the map attribute name and access path can be accessed according to the value of a property of the prop object, looping through the array, which is spliced ​​to the corresponding chunks.

From an implementation point of view and the code amount, the program will be lighter and ingenious, they also do not need to execute a function or dynamically generated by a Function, eval like.

4. Summary

Although the realization of various libraries are different, but the overall idea, the way to achieve high performance stringify are the same:

  1. Developer defines JSON scheme Object of;
  2. stringify corresponding template library generation methods, a method where the template attributes and values ​​based scheme will be string concatenation (Obviously, the efficiency of splicing string attribute access to much higher);
  3. Finally, the developer calls the method returns to stringify Object can be.

In the final analysis, it is essentially static structural information and analysis to optimize the front.

Tips

Finally, still I would like to mention

  • All benchmark only as a reference, if there are specific performance, improve how much it is recommended that you test in the actual business;
  • Use fast-json-stringify into the Function constructor is not recommended as a direct user input scheme, in case some security issues.

Reproduced in: https: //www.jianshu.com/p/f022f81970ae

Guess you like

Origin blog.csdn.net/weixin_34010949/article/details/91177007