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