Skip to content

Instantly share code, notes, and snippets.

@SC-One
Created June 5, 2026 10:36
Show Gist options
  • Select an option

  • Save SC-One/788058e60a82a2a3aae34ebf5a4a74c8 to your computer and use it in GitHub Desktop.

Select an option

Save SC-One/788058e60a82a2a3aae34ebf5a4a74c8 to your computer and use it in GitHub Desktop.
Benchmark JSon parsers Qt and Rapid (QJson & RapidJson)
#include <QCoreApplication>
#include <QFile>
#include <QFileInfo>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QElapsedTimer>
#include <QDebug>
#include <QtGlobal>
#include <rapidjson/document.h>
#include <rapidjson/error/en.h>
static QByteArray readFileAll(const QString &path)
{
QFile file(path);
if (!file.open(QIODevice::ReadOnly)) {
qWarning().noquote() << "Failed to open:" << path;
return {};
}
return file.readAll();
}
static quint64 useQtValue(const QJsonValue &v)
{
quint64 sum = 0;
if (v.isObject()) {
const QJsonObject obj = v.toObject();
sum += static_cast<quint64>(obj.value("id").toInt());
sum += static_cast<quint64>(obj.value("score").toInt());
sum += static_cast<quint64>(obj.value("name").toString().size());
sum += static_cast<quint64>(obj.value("payload").toString().size());
sum += static_cast<quint64>(obj.value("active").toBool() ? 1 : 0);
const QJsonArray tags = obj.value("tags").toArray();
for (const auto &t : tags)
sum += static_cast<quint64>(t.toString().size());
const QJsonObject nested = obj.value("nested").toObject();
sum += static_cast<quint64>(nested.value("level").toInt());
sum += static_cast<quint64>(nested.value("flag").toBool() ? 1 : 0);
sum += static_cast<quint64>(nested.value("note").toString().size());
return sum;
}
if (v.isArray()) {
const QJsonArray arr = v.toArray();
for (const auto &item : arr)
sum += useQtValue(item);
return sum;
}
return sum;
}
static quint64 useRapidValue(const rapidjson::Value &v)
{
quint64 sum = 0;
if (v.IsObject()) {
if (v.HasMember("id") && v["id"].IsInt())
sum += static_cast<quint64>(v["id"].GetInt());
if (v.HasMember("score") && v["score"].IsInt())
sum += static_cast<quint64>(v["score"].GetInt());
if (v.HasMember("name") && v["name"].IsString())
sum += static_cast<quint64>(v["name"].GetStringLength());
if (v.HasMember("payload") && v["payload"].IsString())
sum += static_cast<quint64>(v["payload"].GetStringLength());
if (v.HasMember("active") && v["active"].IsBool())
sum += static_cast<quint64>(v["active"].GetBool() ? 1 : 0);
if (v.HasMember("tags") && v["tags"].IsArray()) {
const auto &tags = v["tags"];
for (auto it = tags.Begin(); it != tags.End(); ++it) {
if (it->IsString())
sum += static_cast<quint64>(it->GetStringLength());
}
}
if (v.HasMember("nested") && v["nested"].IsObject()) {
const auto &nested = v["nested"];
if (nested.HasMember("level") && nested["level"].IsInt())
sum += static_cast<quint64>(nested["level"].GetInt());
if (nested.HasMember("flag") && nested["flag"].IsBool())
sum += static_cast<quint64>(nested["flag"].GetBool() ? 1 : 0);
if (nested.HasMember("note") && nested["note"].IsString())
sum += static_cast<quint64>(nested["note"].GetStringLength());
}
return sum;
}
if (v.IsArray()) {
for (auto it = v.Begin(); it != v.End(); ++it)
sum += useRapidValue(*it);
return sum;
}
return sum;
}
static bool benchQt(const QByteArray &data, int iterations, quint64 &sink, double &avgMs)
{
QElapsedTimer timer;
qint64 totalNs = 0;
for (int i = 0; i < iterations; ++i) {
QJsonParseError err;
timer.restart();
const QJsonDocument doc = QJsonDocument::fromJson(data, &err);
if (err.error != QJsonParseError::NoError) {
qWarning().noquote() << "Qt parse error:" << err.errorString() << "at offset" << err.offset;
return false;
}
sink ^= useQtValue(doc.isArray() ? QJsonValue(doc.array()) : QJsonValue(doc.object()));
totalNs += timer.nsecsElapsed();
}
avgMs = static_cast<double>(totalNs) / 1'000'000.0 / iterations;
return true;
}
static bool benchRapidJson(const QByteArray &data, int iterations, quint64 &sink, double &avgMs)
{
QElapsedTimer timer;
qint64 totalNs = 0;
for (int i = 0; i < iterations; ++i) {
rapidjson::Document doc;
timer.restart();
doc.Parse(data.constData());
if (doc.HasParseError()) {
qWarning().noquote() << "RapidJSON parse error:" << rapidjson::GetParseError_En(doc.GetParseError())
<< "at offset" << doc.GetErrorOffset();
return false;
}
sink ^= useRapidValue(doc);
totalNs += timer.nsecsElapsed();
}
avgMs = static_cast<double>(totalNs) / 1'000'000.0 / iterations;
return true;
}
static int iterationsForSize(qint64 bytes)
{
if (bytes < 100 * 1024)
return 200;
if (bytes < 2 * 1024 * 1024)
return 50;
return 10;
}
static bool runOneFile(const QString &path)
{
const QByteArray data = readFileAll(path);
if (data.isEmpty())
return false;
quint64 sink = 0;
const int iterations = iterationsForSize(data.size());
// A tiny warm-up helps reduce first-run noise.
{
QJsonParseError err;
const QJsonDocument doc = QJsonDocument::fromJson(data, &err);
if (err.error == QJsonParseError::NoError)
sink ^= useQtValue(doc.isArray() ? QJsonValue(doc.array()) : QJsonValue(doc.object()));
rapidjson::Document rdoc;
rdoc.Parse(data.constData());
if (!rdoc.HasParseError())
sink ^= useRapidValue(rdoc);
}
double qtMs = 0.0;
double rapidMs = 0.0;
if (!benchQt(data, iterations, sink, qtMs))
return false;
if (!benchRapidJson(data, iterations, sink, rapidMs))
return false;
qInfo().noquote() << "File:" << QFileInfo(path).fileName()
<< "size:" << data.size() << "bytes"
<< "iterations:" << iterations;
qInfo().noquote() << QString(" Qt avg: %1 ms").arg(qtMs, 0, 'f', 3);
qInfo().noquote() << QString(" RapidJSON avg: %1 ms").arg(rapidMs, 0, 'f', 3);
qInfo().noquote() << QString(" sink: %1").arg(sink);
return true;
}
int main(int argc, char *argv[])
{
QCoreApplication app(argc, argv);
const QStringList args = app.arguments();
if (args.size() < 2) {
qInfo().noquote() << "Usage: qt_rapidjson_benchmark <small.json> <medium.json> <large.json>";
return 0;
}
bool ok = true;
for (int i = 1; i < args.size(); ++i)
ok &= runOneFile(args.at(i));
return ok ? 0 : 1;
}
#include <QCoreApplication>
#include <QDir>
#include <QFile>
#include <QFileInfo>
#include <QTextStream>
#include <QString>
#include <QByteArray>
#include <QtGlobal>
static QByteArray makeObject(int id, int payloadLen)
{
QByteArray payload(payloadLen, 'x');
QByteArray out;
out.reserve(128 + payloadLen);
out += '{';
out += "\"id\":";
out += QByteArray::number(id);
out += ",\"name\":\"item_";
out += QByteArray::number(id);
out += "\",\"active\":";
out += (id % 2 == 0 ? "true" : "false");
out += ",\"score\":";
out += QByteArray::number((id * 17) % 1000);
out += ",\"payload\":\"";
out += payload;
out += "\",\"tags\":[\"qt\",\"json\",\"bench\"],\"nested\":{\"level\":1,\"flag\":true,\"note\":\"hello\"}}";
return out;
}
static bool writeJsonFile(const QString &filePath, qint64 targetBytes, int payloadLen)
{
QFile file(filePath);
if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
qWarning("Failed to open %s", qPrintable(filePath));
return false;
}
const QByteArray sample = makeObject(0, payloadLen);
const qint64 objectLen = sample.size();
const qint64 perItemWithComma = objectLen + 1;
const qint64 count = qMax<qint64>(1, (targetBytes - 2 + perItemWithComma - 1) / perItemWithComma);
file.write("[");
for (qint64 i = 0; i < count; ++i) {
if (i != 0)
file.write(",");
file.write(makeObject(static_cast<int>(i), payloadLen));
}
file.write("]");
file.flush();
file.close();
qInfo().noquote() << QString("Wrote %1 (%2 bytes)").arg(filePath).arg(QFileInfo(filePath).size());
return true;
}
int main(int argc, char *argv[])
{
QCoreApplication app(argc, argv);
const QString baseDir = (app.arguments().size() > 1) ? app.arguments().at(1) : QDir::currentPath();
QDir().mkpath(baseDir);
const QString smallFile = QDir(baseDir).filePath("small.json");
const QString mediumFile = QDir(baseDir).filePath("medium.json");
const QString largeFile = QDir(baseDir).filePath("large.json");
// Target sizes: >10 KB, >1 MB, >10 MB.
// The payload size keeps generation simple and fast while ensuring the thresholds are exceeded.
const bool ok1 = writeJsonFile(smallFile, 12 * 1024, 128);
const bool ok2 = writeJsonFile(mediumFile, 1200 * 1024, 256);
const bool ok3 = writeJsonFile(largeFile, 12 * 1024 * 1024, 384);
return (ok1 && ok2 && ok3) ? 0 : 1;
}

Qt JSON vs RapidJSON Benchmark

Test Setup

The benchmark parses the same JSON files using:

  • Qt JSON (QJsonDocument::fromJson)
  • RapidJSON (rapidjson::Document::Parse)

After parsing, the benchmark traverses the parsed structure and accesses multiple fields to ensure the measurement reflects both parsing and real-world data usage rather than parse-only overhead.

Test Files

File Size
small.json 12,734 bytes
medium.json 1,252,725 bytes
large.json 12,811,142 bytes

Release Build Results

File Size Qt (ms) RapidJSON (ms) Speedup
small.json 12,734 B 0.110 0.030 3.67×
medium.json 1,252,725 B 7.829 2.665 2.94×
large.json 12,811,142 B 65.212 20.005 3.26×

Release Summary

RapidJSON consistently outperformed Qt JSON in Release builds:

  • ~3.7× faster on the small dataset.
  • ~2.9× faster on the medium dataset.
  • ~3.3× faster on the large dataset.

The performance advantage remains relatively stable as file size increases, indicating lower parsing overhead and more aggressive optimization in Release mode.


Debug Build Results

File Size Qt (ms) RapidJSON (ms) Qt Advantage
small.json 12,734 B 0.125 0.262 2.10× faster
medium.json 1,252,725 B 8.652 18.763 2.17× faster
large.json 12,811,142 B 70.990 161.291 2.27× faster

Debug Summary

In Debug builds, Qt JSON significantly outperformed RapidJSON:

  • ~2.1× faster on the small dataset.
  • ~2.2× faster on the medium dataset.
  • ~2.3× faster on the large dataset.

This result suggests RapidJSON is substantially more dependent on compiler optimizations, while Qt JSON maintains comparatively better Debug-build performance.


Overall Observations

Build Type Faster Library
Release RapidJSON
Debug Qt JSON

Key Takeaways

  1. Release builds

    • RapidJSON delivers approximately 3× better performance than Qt JSON across all tested file sizes.
    • Recommended for performance-critical production workloads.
  2. Debug builds

    • Qt JSON performs significantly better than RapidJSON.
    • Faster iteration and debugging experience during development.
  3. Production recommendation

    • If maximum JSON parsing throughput is required, RapidJSON is the stronger choice when applications are compiled in Release mode.
    • For applications where simplicity, integration with Qt types, and debugging convenience are priorities, Qt JSON remains a strong option.

Raw Benchmark Output

Release

File: small.json size: 12734 bytes iterations: 200
  Qt       avg: 0.110 ms
  RapidJSON avg: 0.030 ms

File: medium.json size: 1252725 bytes iterations: 50
  Qt       avg: 7.829 ms
  RapidJSON avg: 2.665 ms

File: large.json size: 12811142 bytes iterations: 10
  Qt       avg: 65.212 ms
  RapidJSON avg: 20.005 ms

Debug

File: small.json size: 12734 bytes iterations: 200
  Qt       avg: 0.125 ms
  RapidJSON avg: 0.262 ms

File: medium.json size: 1252725 bytes iterations: 50
  Qt       avg: 8.652 ms
  RapidJSON avg: 18.763 ms

File: large.json size: 12811142 bytes iterations: 10
  Qt       avg: 70.990 ms
  RapidJSON avg: 161.291 ms
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment