Savez-vous comment améliorer les performances de JSON.stringify() ?

Dachang Technology Nœud Frontal Avancé Avancé

Cliquez sur le guide de croissance du meilleur programmeur, faites attention au numéro public

Répondre 1, rejoignez le groupe d'échange avancé Node

Lien d'origine : https://juejin.im/post/5cf61ed3e51d4555fd20a2f3

Auteur : AlienZHOU

1. FamilierJSON.stringify

Côté navigateur ou côté serveur, JSON.stringify()c'est notre méthode très courante :

  • Stockez l'objet JSON dans localStorage ;

  • Corps JSON dans la requête POST ;

  • Traiter les données sous forme de JSON dans le corps de la réponse ;

  • Même sous certaines conditions, nous l'utiliserons pour implémenter une simple copie profonde ;

  • ……

Dans certaines situations sensibles aux performances (par exemple, le serveur gère beaucoup de simultanéité), ou face à un grand nombre d'opérations de stringification, nous aimerions qu'il fonctionne mieux et plus rapidement. Cela a également engendré des solutions/bibliothèques stringify optimisées, et la figure suivante montre leurs performances par rapport aux méthodes natives :

8a9c98d91dc15e4f3bbe39d6532e6c7c.png

La partie verte est native JSON.stringify()et les performances visibles sont bien inférieures à ces bibliothèques. Alors, quelle est la justification technique derrière l'amélioration spectaculaire des performances ?

2. stringifyplus rapidestringify

Comme JavaScript est un langage très dynamique, pour une variable de type Object, le nom de clé, la valeur de clé et le type de valeur de clé qu'elle contient ne peuvent être déterminés qu'au moment de l'exécution. Il JSON.stringify()y a donc beaucoup de travail à faire lors de l'exécution. Il n'y a rien que nous puissions faire pour optimiser radicalement sans le savoir.

Donc, si nous connaissons le nom de la clé et les informations sur la valeur de la clé dans cet objet - c'est-à-dire, connaissons ses informations de structure, cela nous aidera-t-il ?

Voir un exemple :

L'Objet suivant,

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

Nous l'appliquons pour JSON.stringify()obtenir le résultat tel que

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

Maintenant, si nous savons que objla structure de ceci est fixe :

  • nom de clé inchangé

  • Le type de la valeur de la clé doit être

En fait, je peux créer une méthode stringify "personnalisée"

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

Jetez un oeil à myStringifyla sortie de notre méthode:

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}

Le résultat correct est obtenu, mais seules la conversion de type et la concaténation de chaînes sont utilisées, de sorte que la méthode "personnalisée" peut rendre "stringify" plus rapide.

Pour résumer, comment obtenir stringifyune stringifyméthode plus rapide que ?

  1. Les informations de structure de l'objet doivent être déterminées en premier ;

  2. Selon ses informations de structure, une méthode "personnalisée" stringifyest créée pour l'objet de cette structure, et le résultat interne est en fait généré par concaténation de chaînes ;

  3. Enfin, utilisez la méthode "custom" pour stringifier l'objet.

C'est également la routine de la plupart des bibliothèques d'accélération stringify, qui se traduit par un code comme celui-ci :

import faster from 'some_library_faster_stringify';

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

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

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

3. Comment générer des méthodes "personnalisées"

Selon l'analyse ci-dessus, la fonction principale est de créer une méthode stringify "personnalisée" pour cette classe d'objets en fonction de ses informations de structure, qui est en fait un simple accès aux attributs et un épissage de chaîne.

Afin de comprendre la méthode d'implémentation spécifique, permettez-moi de présenter brièvement deux bibliothèques open source avec des implémentations légèrement différentes à titre d'exemples.

3.1. rapide-json-stringifier

128d38b4d650b8b8e17ce72caf78d5de.png

La figure suivante est une comparaison des performances basée sur les résultats de référence fournis par fast-json-stringify [1] .

566a1fc331852318a98f0d80f6612a70.png

On peut voir qu'il y a une amélioration des performances de 2 à 5 fois dans la plupart des scénarios.

3.1.1. Comment le schéma est défini

fast-json-stringify utilise JSON Schema Validation [2] pour définir le format de données des objets (JSON). La structure définie par son schéma lui-même est également au format JSON, comme object

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

Le schéma correspondant est :

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

Ses règles de définition de schéma sont riches, et l'utilisation spécifique peut se référer à la bibliothèque de vérification JSON Ajv [3] .

3.1.2 Génération de la méthode stringify

fast-json-stringify générera la chaîne de code de fonction réelle en épissant selon le schéma qui vient d'être défini, puis utilisera le constructeur Function [4] pour générer dynamiquement la fonction stringify correspondante lors de l'exécution.

En génération de code, tout d'abord, il va injecter différentes méthodes outils prédéfinies, les différents schémas de cette partie sont les mêmes :

var code = `'use strict'`

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

Dans un second temps, le code spécifique de la fonction stringify sera généré en fonction du contenu spécifique défini par le schéma. La manière de générer est également relativement simple : en parcourant le schéma.

Lors de la traversée du schéma, selon le type défini, insérez la fonction d'outil correspondante au niveau du code correspondant pour la conversion clé-valeur. namePar exemple, cette propriété dans l' exemple ci -dessus :

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
    ……

Les variables dans le code ci-dessus codecontiennent la chaîne de code du corps de fonction généré final. Puisque dans la définition du schéma, il names'agit d'un stringtype et non vide, la codechaîne de code suivante lui sera ajoutée :

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

Le code réel est souvent omis en raison de la complexité du traitement des tableaux, des objets concaténés, etc.

Ensuite, la codechaîne complète résultante est à peu près la suivante :

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

Enfin, passez la codechaîne au constructeur Function pour créer la fonction stringify correspondante.

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

3.2. slow-json-stringify

a9f71867201074eb57568d94ac25a4e6.png

slow-json-stringify [5] , malgré le nom "slow", est en fait une bibliothèque stringify "rapide" (avec un nom coquin).

Le stringificateur le plus lent de l'univers connu. Je plaisante, c'est le plus rapide (:

Son implémentation est plus légère que le fast-json-stringify susmentionné, et l'idée est très intelligente. En même temps, il sera plus rapide que fast-json-stringify dans de nombreux scénarios [6] .

1f248b87fced8126618ec3578426aadd.png 36ab80e437942ee4ef5d4bd95ffefc39.png

3.2.1. Comment le schéma est défini

La définition du schéma de slow-json-stringify est plus naturelle et simple, remplaçant principalement les valeurs clés par des descriptions de type. Ou l'exemple de l'objet ci-dessus, le schéma deviendra

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

Très intuitif en effet.

3.2.2 Génération de la méthode stringify

je ne sais pas si tu as remarqué

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

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

La structure du schéma et l'objet d'origine sont-ils très similaires ?

La chose intelligente à propos de ce schéma est qu'après cette définition, nous pouvons d'abord mettre le schéma JSON.stringify, puis "déduire" toutes les valeurs de type, et enfin ce qui nous attend est de remplir directement la valeur réelle dans la déclaration de type correspondant au schéma .

Comment ça marche?

Tout d'abord, vous pouvez appeler directement le schéma JSON.stringify()pour générer le modèle de base et emprunter JSON.stringify()le deuxième paramètre comme chemin d'accès de la méthode de traversée pour collecter les propriétés :

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;
});

À ce stade, maples chemins d'accès de toutes les propriétés sont collectés dans le fichier. Dans le même temps, les expressions régulières générées propspeuvent être fusionnées en chaînes correspondantes du type correspondant, comme l'expression régulière dans notre exemple /"name"|"status"|"working"|"(string|number|boolean|undef)"|\\[(.*?)\\]/.

Faites ensuite correspondre séquentiellement ces attributs en fonction de l'expression régulière, remplacez la chaîne de type d'attribut par une chaîne d'espace réservé uniforme "__par__"et "__par__"divisez la chaîne en fonction de :

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__');

De cette façon, vous obtiendrez une chunkssomme et propsdeux tableaux. chunkscontient la chaîne JSON fractionnée. Par exemple, les deux tableaux sont les suivants

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

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

Enfin, étant donné que le mappage entre les noms de propriété et les chemins d'accès est stocké dans la carte, la valeur d'une propriété dans l'objet peut être consultée en fonction de la prop, bouclée dans le tableau et épissée avec les morceaux correspondants.

Du point de vue de la taille et de l'implémentation du code, cette solution sera plus légère et plus ingénieuse, et elle n'a pas besoin de générer ou d'exécuter dynamiquement des fonctions via Function, eval, etc.

4. Résumé

Bien que l'implémentation de différentes bibliothèques soit différente de l'idée générale, la façon d'obtenir un stringify hautes performances est la même :

  1. Le développeur définit le schéma JSON d'Object ;

  2. La bibliothèque stringify génère la méthode de modèle correspondante selon le schéma, et la méthode de modèle effectuera l'épissage de chaîne des attributs et des valeurs (évidemment, l'efficacité de l'accès aux attributs et de l'épissage de chaîne est beaucoup plus élevée) ;

  3. Enfin, le développeur peut appeler la méthode renvoyée pour stringifier Object.

En dernière analyse, il ajoute essentiellement à l'optimisation et à l'analyse des informations structurelles statiques.

Conseils

Enfin, je voudrais mentionner

  • Tous les benchmarks ne peuvent être utilisés que comme référence, s'il y a une amélioration des performances et dans quelle mesure, ou s'il est recommandé de tester dans une entreprise réelle ;

  • Le constructeur Function est utilisé dans fast-json-stringify, il est donc recommandé de ne pas utiliser l'entrée utilisateur directement comme schéma pour éviter certains problèmes de sécurité.

Node 社群


我组建了一个氛围特别好的 Node.js 社群,里面有很多 Node.js小伙伴,如果你对Node.js学习感兴趣的话(后续有计划也可以),我们可以一起进行Node.js相关的交流、学习、共建。下方加 考拉 好友回复「Node」即可。

如果你觉得这篇内容对你有帮助,我想请你帮我2个小忙:

1. 点个「在看」,让更多人也能看到这篇文章2. 订阅官方博客 www.inode.club 让我们一起成长

点赞和在看就是最大的支持❤️

Les références

[1]

https://github.com/fastify/fast-json-stringify#benchmarks : https://github.com/fastify/fast-json-stringify#benchmarks

[2]

http://json-schema.org/latest/json-schema-validation.html : http://json-schema.org/latest/json-schema-validation.html

[3]

https://ajv.js.org/ : https://ajv.js.org/

[4]

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Function : https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/ Une fonction

[5]

https://github.com/lucagez/slow-json-stringify : https://github.com/lucagez/slow-json-stringify

[6]

https://github.com/lucagez/slow-json-stringify/blob/master/benchmark.md : https://github.com/lucagez/slow-json-stringify/blob/master/benchmark.md

Je suppose que tu aimes

Origine blog.csdn.net/xgangzai/article/details/123785117
conseillé
Classement