Im ersten Teil einer Artikelserie ging der Autor auf die Problematik des Tunings ein und Zusammenführen von Nachrichten und Reduzieren ihrer Last in Telemetriesensoren.
In diesem Teil geht es um Nachrichtennutzlast und deren Optimierung.
Es gibt viele Methoden, um ein Objekt mit Qt zu serialisieren. Im ersten Teil der Artikelserie haben wir JSON verwendet. Dazu werden alle Informationen über den Sensor in QJsonObject gespeichert und QJsonDocument kümmert sich um den Fluss der Werte in QByteArray.
QJsonObject jobject; jobject[ "SensorID" ] = m_id; jobject[ "AmbientTemperature" ] = m_ambientTemperature; jobject[ "ObjectTemperature" ] = m_objectTemperature; jobject[ "AccelerometerX" ] = m_accelerometerX; jobject[ "AccelerometerY" ] = m_accelerometerY; jobject[ "AccelerometerZ" ] = m_accelerometerZ; jobject[ "Altitude" ] = m_altitude; jobject[ "Light" ] = m_light; jobject[ "Humidity" ] = m_humidity; QJsonDocument doc( jobject ); return doc.toJson();
JSON hat mehrere Vorteile:
- Text JSON ist beschreibend und für Menschen lesbar;
- und die Informationen strukturiert sind;
- der Austausch grundlegender Informationen ist einfach und klar;
- Mit JSON können Sie Nachrichten mit zusätzlichen Werten erweitern;
- Es gibt viele Lösungen zum Analysieren und Analysieren von JSON in der Cloud.
Dieser Ansatz hat jedoch einige Einschränkungen. Erstens kann das Erstellen einer JSON-Nachricht ein schwerer Vorgang sein, der viele Zyklen dauert. Der Benchmark des zweiten Teils des Tests zeigt, dass die Serialisierung und Deserialisierung von 10.000 Nachrichten etwa 263 ms dauert. Es mag nicht nach vielen Botschaften klingen, aber in diesem Zusammenhang ist Zeit gleichbedeutend mit Energie. Dies kann den Sensor, der für eine lange Zeit ohne Aufladen ausgelegt ist, erheblich beeinträchtigen.
Ein weiterer Aspekt ist, dass die Nutzlast für die MQTT-Nachricht zur Sensoraktualisierung 346 Bytes beträgt. Da der Sensor nur acht Takes und eine begrenzte Zeile sendet, kann dies ein potenziell großer Rechenaufwand sein.
In den Kommentaren des vorherigen Beitrags wird empfohlen, QJsonDocument : Compact (compact) zu verwenden, wodurch die Größe der Nutzlast im Durchschnitt auf 290 Bytes reduziert wird
Wie kann dies verbessert werden?
Wie die meisten von Ihnen wissen, gibt es auch binäres JSON, das die Lesbarkeit beeinträchtigen kann, aber alle anderen Aspekte sind immer noch relevant. Das Wichtigste beim Ausführen von Benchmarks ist, dass ein einfacher doc.toJson-Schalter in doc.toBinaryData die Geschwindigkeit des Tests verdoppelt und die Benchmark-Iteration auf 125 Millisekunden reduziert.
Nach Überprüfung der Last beträgt die Nachrichtengröße 338 Byte, der Unterschied ist fast nicht wahrnehmbar. Dies kann sich jedoch in verschiedenen Szenarien ändern, beispielsweise wenn weitere Zeilen innerhalb der Nachricht hinzugefügt werden.
Je nach Anforderung können Drittprojekte und andere Features hinzugefügt werden.
Falls das Projekt „innerhalb der Qt-Welt“ ist, ist der gesamte Datenstrom definiert und ändert QDataStream nicht, ist eine mögliche Option.
Wir fügen der SensorInformation-Klasse Unterstützung dafür hinzu, was das Hinzufügen von zwei Anweisungen erfordert.
QDataStream &operator<<(QDataStream &, const SensorInformation &); QDataStream &operator>>(QDataStream &, SensorInformation &);
Die Implementierung ist recht einfach, unten ist ein Beispiel für die Serialisierung:
QDataStream &operator<<(QDataStream &out, const SensorInformation &item) { QDataStream::FloatingPointPrecision prev = out.floatingPointPrecision(); out.setFloatingPointPrecision(QDataStream::DoublePrecision); out << item.m_id << item.m_ambientTemperature << item.m_objectTemperature << item.m_accelerometerX << item.m_accelerometerY << item.m_accelerometerZ << item.m_altitude << item.m_light << item.m_humidity; out.setFloatingPointPrecision(prev); return out; }
Bei Verwendung von QDataStream betrug die Benchmark-Zeit 26 Millisekunden, was fast 10-mal schneller ist als Text-JSON. Außerdem beträgt die durchschnittliche Nachrichtengröße nur 84 Bytes im Vergleich zu 290. Wenn die oben genannten Grenzen akzeptabel sind, ist QDataStream daher eine gute Option für die anstehende Aufgabe.
Wenn das Projekt das Hinzufügen zusätzlicher Komponenten von Drittanbietern zulässt, ist eine der bekanntesten Serialisierungslösungen das Google Buffer Protocol (protobuf).
Um protobuf zu unserer Lösung hinzuzufügen, müssen wir einige Änderungen vornehmen. Erstens verwendet protobuf IDL, um Datenstrukturen oder Nachrichten zu beschreiben. Dann die Entwicklung des Informationssensors:
syntax = "proto2" ; package serialtest; message Sensor { required string id = 1; required double ambientTemperature = 2; required double objectTemperature = 3; required double accelerometerX = 4; required double accelerometerY = 5; required double accelerometerZ = 6; required double altitude = 7; required double light = 8; required double humidity = 9; }
Um einem qmake-Projekt einen protobuf (Protokoll)-Codegenerator hinzuzufügen, müssen Sie einen zusätzlichen Compiler-Schritt wie diesen hinzufügen:
PROTO_FILE = sensor.proto protoc.output = $${OUT_PWD}/${QMAKE_FILE_IN_BASE}.pb.cc protoc.commands = $${PROTO_PATH}/bin/protoc -I=$$relative_path($${PWD}, $${OUT_PWD}) --cpp_out=. ${QMAKE_FILE_NAME} protoc.variable_out = GENERATED_SOURCES protoc.input = PROTO_FILE QMAKE_EXTRA_COMPILERS += protoc
Um dann einen vergleichbaren Benchmark in Bezug auf die Objektgröße zu haben, wird eine generierte Struktur als Mitglied für die SensorInformationProto-Klasse verwendet, die QObject erbt, wie im QDataStream- und JSON-Beispiel
class SensorInformationProto : public QObject { Q_OBJECT Q_PROPERTY( double ambientTemperature READ ambientTemperature WRITE setAmbientTemperature NOTIFY ambientTemperatureChanged) [...] public : SensorInformationProto( const std::string &pr); [...] std::string serialize() const ; [...] private : serialtest::Sensor m_protoInfo; };
Die Proto-Info-Serialisierungsfunktion wird vom Protokoll generiert, daher ist der Schritt zum Erstellen der zu übergebenden Nutzdaten wie folgt:
std::string SensorInformationProto::serialize() const { std::string res; m_protoInfo.SerializeToString(&res); return res; }
Beachten Sie, dass protobuf im Vergleich zu früheren Lösungen eine STD-Zeichenfolge verwendet. Dies bedeutet, dass Sie die Fähigkeiten von QString verlieren, es sei denn, die Zeichenfolge wird als Byte-Array gespeichert (manuelle Konvertierung erforderlich). Auch dies verlangsamt den gesamten Prozess aufgrund des Parsens.
Aus Performance-Sicht sehen die Benchmark-Ergebnisse vielversprechend aus. Ein Benchmark mit 10.000 Elementen dauert nur 5 ms bei einer durchschnittlichen Nachrichtengröße von 82 Bytes.
Als Fazit zeigt die folgende Tabelle die unterschiedlichen Ansätze zur Serialisierung:
| | Nutzlastgröße | Zeit (ms) |
| json (Text) | 346 | 263 |
| json (binär) | 338 | 125 |
| QDataStream | 84 | 26 |
| Protobuf | 82 | 5 |
Eine vielversprechende Alternative ist CBOR, das derzeit von Thiago Macieira für Qt 5.12 implementiert wird. Da der Entwicklungsprozess jedoch weitergeht, ist es noch zu früh, ihn in diesen Artikel aufzunehmen. Die Ergebnisse der Diskussionen sehen vielversprechend aus, mit einer erheblichen Leistung gegenüber JSON und all seinen Vorteilen.
Der Artikel zeigte verschiedene Ansätze zur Serialisierung von Daten in MQTT-Nachrichtennutzlasten. Dies kann ausschließlich innerhalb von Qt oder mit externen Lösungen (zB protobuf) erfolgen. Die Integration externer Lösungen in Qt ist einfach.
Es ist erwähnenswert, dass alle Serialisierungen, die mit dem Benchmark durchgeführt wurden, mit einer kleinen Datenmenge in der Nachricht durchgeführt wurden, und wenn die Datenmenge größer ist, können die Ergebnisse abweichen und zu unterschiedlichen Ansätzen führen bessere Ergebnisse.