Maison >interface Web >js tutoriel >Analyse des performances de JSON, Buffer/Custom Binary Protocol, Protobuf et MessagePack pour Websockets

Analyse des performances de JSON, Buffer/Custom Binary Protocol, Protobuf et MessagePack pour Websockets

DDD
DDDoriginal
2024-11-03 07:28:03290parcourir

Cet article examine et compare les méthodes/formats de sérialisation et désérialisation des données : JSON, Buffers (protocole binaire personnalisé), Protobuf et MessagePack, et propose des conseils sur la façon de les implémenter. . (Repère de performance à la fin)

JSON

La méthode d'envoi de messages la plus courante est JSON. Où vous encodez des données dans une chaîne afin qu'elles puissent être transmises via un message Websocket et les analyser.

ws.send(JSON.stringify({greeting: "Hello World"]}))

ws.on("message", (message) => {
    const data = JSON.parse(message);
    console.log(data)
})

Protocole binaire personnalisé

Un protocole binaire personnalisé est une implémentation personnalisée légère de la sérialisation et de la désérialisation des données. Il est couramment utilisé lorsque la vitesse, les performances et la faible latence sont cruciales, par ex. jeux multijoueurs en ligne et plus (ou si vous souhaitez optimiser votre application). Lors de la création d'un protocole binaire personnalisé, vous travaillez avec des tampons et du binaire, ce qui peut être difficile à mettre en œuvre, mais si vous avez des connaissances en tampons et en binaire, cela ne devrait poser aucun problème.

const encoder = new TextEncoder();
const decoder = new TextDecoder();

function binary(text, num) {
    const messageBytes = encoder.encode(text);
    // array buffers cannot store strings, so you must encode
    // the text first into an array of binary values
    // e.g. "Hello World!" -> [72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33]

    const buffer = new ArrayBuffer(1 + messageBytes.length);
    // when creating array buffers,
    //you must predetermine the size of your array buffer

    const view = new DataView(buffer);
    // create a view to interact with the buffer

    view.setUint8(0, num);

    const byteArray = new Uint8Array(buffer);
    byteArray.set(messageBytes, 1);

    return buffer;
}

ws.on("open", () => {
    const msg = binary("Hello World!", 123);
    ws.send(msg);
})

ws.on("message", (message) => {
    const buffer = message.buffer;
    const view = new DataView(buffer);
    const num = view.getUint8(0);
    const textLength = buffer.byteLength - 1
    const textBytes = new Uint8Array(buffer, 1, textLength);
    const text = decoder.decode(textBytes);

    console.log(text, num);
});

Cette fonction sérialise deux propriétés, l'une étant du texte et l'autre étant un nombre dans un tampon de tableau.

Protobuf

Dans cet exemple de code, j'utilise protobuf.js, une implémentation javascript de protobufs. J'utilise la réflexion pour générer le code protobuf au moment de l'exécution. Vous pouvez également générer du code de manière statique, mais cela n'a aucun impact sur les performances selon le wiki protobuf.js, cependant, il charge le code protobuf plus rapidement, mais cela n'a aucun impact sur les performances lors de l'envoi de messages websocket.

syntax = "proto3";

message TestMessage {
    string text = 1;
    uint32 num = 2;
}
import protobuf from "protobufjs";

const ws = new Websocket("ws://localhost:3000");
ws.binaryType = "arraybuffer";

protobuf.load("testmessage.proto", function (err, root) {
    if (err)
        throw err;

    if (root === undefined) return;

    const TestMessage = root.lookupType("TestMessage")

    ws.on("open", () => {
        const message = TestMessage.create({text: "Hello World!", num: 12345});
        const buffer = TestMessage.encode(message).finish();
        ws.send(buffer);
    });

    ws.on("message", (msg) => {
        const buffer = new Uint8Array(msg);
        const data = TestMessage.decode(buffer);
        console.log(data.text);
        console.log(data.num);
    });
})

MessagePack

import { encode, decode } from "@msgpack/msgpack";

ws.binaryType = "arraybuffer"

ws.on("open", () => {
    const msg = encode({"Hello World!", 123});
    ws.send(msg);
})

ws.on("message", (msg) => {
    const data = decode(msg);
    console.log(data);
})

Référentiel de performances

Pour comparer les performances de chaque format/méthode de sérialisation de données, j'ai écrit un benchmark qui mesure les performances lors de l'envoi de données via des Websockets.

J'ai divisé les benchmarks en différents groupes.

  • Petite saisie de données

  • Saisie de données moyenne

  • Saisie Big Data

Il s'agit de mesurer les performances de ces sérialisations de données sur différentes tailles de données. J'ai également enregistré les performances de la sérialisation, de la désérialisation et de la durée totale pour chaque groupe. J'ai effectué les benchmarks exacts 5 fois pour chaque groupe et calculé la moyenne pour garantir la fiabilité de ces tests.

Le benchmark envoie des messages Websocket en 100 000 itérations. Le code est écrit en Bun.js

Ces repères ont été enregistrés dans le temps pour terminer (ms), donc plus petit est plus rapide.

Benchmark de petites données

ws.send(JSON.stringify({greeting: "Hello World"]}))

ws.on("message", (message) => {
    const data = JSON.parse(message);
    console.log(data)
})

Taille en octets de chaque format de sérialisation

Method Byte size (bytes)
JSON 33
Custom Binary Protocol 13
Protobuf 17
MessagePack 24

Temps total (ms)

Performance Analysis of JSON, Buffer / Custom Binary Protocol, Protobuf, and MessagePack for Websockets

Temps de sérialisation (ms)

Performance Analysis of JSON, Buffer / Custom Binary Protocol, Protobuf, and MessagePack for Websockets

Temps de désérialisation (ms)

Performance Analysis of JSON, Buffer / Custom Binary Protocol, Protobuf, and MessagePack for Websockets

Benchmark de données moyennes

const encoder = new TextEncoder();
const decoder = new TextDecoder();

function binary(text, num) {
    const messageBytes = encoder.encode(text);
    // array buffers cannot store strings, so you must encode
    // the text first into an array of binary values
    // e.g. "Hello World!" -> [72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33]

    const buffer = new ArrayBuffer(1 + messageBytes.length);
    // when creating array buffers,
    //you must predetermine the size of your array buffer

    const view = new DataView(buffer);
    // create a view to interact with the buffer

    view.setUint8(0, num);

    const byteArray = new Uint8Array(buffer);
    byteArray.set(messageBytes, 1);

    return buffer;
}

ws.on("open", () => {
    const msg = binary("Hello World!", 123);
    ws.send(msg);
})

ws.on("message", (message) => {
    const buffer = message.buffer;
    const view = new DataView(buffer);
    const num = view.getUint8(0);
    const textLength = buffer.byteLength - 1
    const textBytes = new Uint8Array(buffer, 1, textLength);
    const text = decoder.decode(textBytes);

    console.log(text, num);
});

Taille en octets du message dans chaque format de sérialisation

Method Byte size (bytes)
JSON 117
Custom Binary Protocol 70
Protobuf 75
MessagePack 102

Temps total (ms)

Performance Analysis of JSON, Buffer / Custom Binary Protocol, Protobuf, and MessagePack for Websockets

Sérialisation (ms)

Performance Analysis of JSON, Buffer / Custom Binary Protocol, Protobuf, and MessagePack for Websockets

Désérialisation (ms)

Performance Analysis of JSON, Buffer / Custom Binary Protocol, Protobuf, and MessagePack for Websockets

Benchmark des mégadonnées

syntax = "proto3";

message TestMessage {
    string text = 1;
    uint32 num = 2;
}

Taille en octets du message dans chaque format de sérialisation

Method Byte size (bytes)
JSON 329
Custom Binary Protocol 220
Protobuf 229
MessagePack 277

Temps total (ms)

Performance Analysis of JSON, Buffer / Custom Binary Protocol, Protobuf, and MessagePack for Websockets

Sérialisation (ms)

Performance Analysis of JSON, Buffer / Custom Binary Protocol, Protobuf, and MessagePack for Websockets

Désérialisation (ms)

Performance Analysis of JSON, Buffer / Custom Binary Protocol, Protobuf, and MessagePack for Websockets

MessagePack a soudainement cessé de fonctionner après environ 6 600 messages envoyés.

Analyse des benchmarks

Dans tous les benchmarks, le protocole binaire personnalisé est le plus rapide dans le temps total et a la taille d'octet la plus petite/la plus efficace lors de la sérialisation messages. Cependant, les différences de performances sont significatives.

Étonnamment, le temps de sérialisation de JSONest considérablement plus rapide que la sérialisation du Protocole binaire personnalisé. C'est probablement parce que JSON.stringify() est implémenté en c natif avec Node et en zig natif avec Bun. Les résultats peuvent également varier lors de l'utilisation de Node, car JSON.stringify() avec Bun est 3,5 fois plus rapide que Node.

MessagePack pourrait potentiellement être plus rapide car dans ce benchmark, j'ai utilisé l'implémentation javascript officielle de MessagePack. Il existe d'autres implémentations de MessagePack potentiellement plus rapides, telles que MessagePackr.

Merci d'avoir lu !


Benchmark (écrit en dactylographié) : https://github.com/nate10j/buffer-vs-json-websocket-benchmark.git

Voir les résultats ici dans les feuilles Google.

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn