Commit f6b257d8 authored by EXT Arnaud Clère's avatar EXT Arnaud Clère
Browse files

Added QCborWriter which is 2 to 3 times more efficient than QDebug

parent b451a058
......@@ -25,3 +25,6 @@ DEFINES += QT_DEPRECATED_WARNINGS
SOURCES += \
main.cpp
HEADERS += \
QCborWriter.hpp
/****************************************************************************
* **
* ** Copyright (C) 2017 MinMaxMedical.
* ** All rights reserved.
* ** Contact: MinMaxMedical <InCAS@MinMaxMedical.com>
* **
* ** This file is part of the modmedLog module.
* **
* ** $QT_BEGIN_LICENSE:BSD$
* ** You may use this file under the terms of the BSD license as follows:
* **
* ** "Redistribution and use in source and binary forms, with or without
* ** modification, are permitted provided that the following conditions are
* ** met:
* ** * Redistributions of source code must retain the above copyright
* ** notice, this list of conditions and the following disclaimer.
* ** * Redistributions in binary form must reproduce the above copyright
* ** notice, this list of conditions and the following disclaimer in
* ** the documentation and/or other materials provided with the
* ** distribution.
* ** * Neither the name of MinMaxMedical S.A.S. and its Subsidiary(-ies) nor
* ** the names of its contributors may be used to endorse or promote
* ** products derived from this software without specific prior written
* ** permission.
* **
* ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
* ** $QT_END_LICENSE$
* **
* ****************************************************************************/
#pragma once
#include <type_traits>
#include <QtCore/qiodevice.h>
#include <QtCore/qendian.h>
namespace cbor {
//! CBOR Major types
//! Encoded in the high 3 bits of the descriptor byte
//! \see http://tools.ietf.org/html/rfc7049#section-2.1
enum MajorTypes {
TextStringType = 3,
ArrayType = 4,
SimpleTypesType = 7
};
//! CBOR simple and floating point types
//! Encoded in the low 5 bits of the descriptor byte when the Major Type is 7.
enum SimpleTypes {
NullValue = 22,
SinglePrecisionFloat = 26,
DoublePrecisionFloat = 27,
Break = 31
};
enum {
SmallValueBitLength = 5U,
Value8Bit = 24U,
Value16Bit = 25U,
Value32Bit = 26U,
Value64Bit = 27U,
IndefiniteLength = 31U,
MajorTypeShift = SmallValueBitLength,
};
enum : unsigned char {
BreakByte = (unsigned)Break | (SimpleTypesType << MajorTypeShift),
IndefiniteLengthArrayByte = IndefiniteLength | (ArrayType << MajorTypeShift),
NullByte = NullValue | (SimpleTypesType << MajorTypeShift)
};
enum tag{
Tag = 192,
DateTime = 0,
CborDocument = 55799,
};
} // cbor
class QCborWriter
{
class Impl;
Q_DISABLE_COPY(QCborWriter) // but not swap
public:
Q_ENABLE_SWAP(QCborWriter, std::swap(m, o.m);)
using TResult = QCborWriter;
using TValue = QCborWriter::Impl*;
using TSequence = QCborWriter::Impl*;
static const BindMode Mode = BindMode::Write;
QCborWriter(QIODevice* io) : m(new Impl(io)) {}
~QCborWriter() { if (m) delete m; }
Val<QCborWriter> begin() { auto beforeMove = m; return std::move(Val<QCborWriter>(beforeMove, std::move(*this))); }
// Shortcuts
Seq<QCborWriter> sequence() { return begin().sequence(); }
private:
Impl* m = nullptr;
class Impl {
private:
QIODevice* io = nullptr;
int levels = 0; //!< minimal dynamic context to implement out() and ensure well-formedness in case TResult is abandoned
public:
Impl(QIODevice* io) : io(io) {}
~Impl() { while (levels) out(); }
protected:
// static "interface" implementation
template<class T_> friend class Val;
Impl* sequence() { levels++;
io->putChar(cbor::IndefiniteLengthArrayByte); return this; }
void null() { io->putChar(cbor::NullByte); }
void bind(const char* s) { putInteger (cbor::TextStringType, strlen(s));
io->write(s); }
// Natively supported overloads
void bind( float n) { io->putChar((cbor::SimpleTypesType << cbor::MajorTypeShift) | cbor::SinglePrecisionFloat);
union { float value; quint32 bits; } number;
number.value = n;
char bytes[sizeof(number.bits)];
qToBigEndian(number.bits, bytes);
io->write(bytes, sizeof(bytes)); }
void bind( double n) { io->putChar((cbor::SimpleTypesType << cbor::MajorTypeShift) | cbor::DoublePrecisionFloat);
union { double value; quint64 bits; } number;
number.value = n;
char bytes[sizeof(number.bits)];
qToBigEndian(number.bits, bytes);
io->write(bytes, sizeof(bytes)); }
// This dispatch would be more simple with C++17 constexpr if
template<typename T> void bind(T t) {
QBind<TResult,T>::bind(Val<TResult>(this, TResult()), t); // Val<TResult> prevents QBind<TResult,T>() from getting out() of an outer Seq for instance
}
// static "interface" implementation
template<class T_> friend class Seq;
Impl* item() { return this; }
Impl* out() { io->putChar(cbor::BreakByte); levels--; return this; }
private:
void putInteger(char majorType, quint64 n)
{
// See https://tools.ietf.org/html/rfc7049#section-2.2 if more performance is needed
if (n < 24) {
io->putChar((majorType << cbor::MajorTypeShift) | n);
} else {
char bytes[8];
if (n <= 0xff ) { io->putChar((majorType << cbor::MajorTypeShift) | cbor::Value8Bit ); qToBigEndian(quint8 (n),bytes); io->write(bytes,sizeof(quint8 )); }
else if (n <= 0xffff ) { io->putChar((majorType << cbor::MajorTypeShift) | cbor::Value16Bit); qToBigEndian(quint16(n),bytes); io->write(bytes,sizeof(quint16)); }
else if (n <= 0xffffffffL) { io->putChar((majorType << cbor::MajorTypeShift) | cbor::Value32Bit); qToBigEndian(quint32(n),bytes); io->write(bytes,sizeof(quint32)); }
else { io->putChar((majorType << cbor::MajorTypeShift) | cbor::Value64Bit); qToBigEndian( n ,bytes); io->write(bytes,sizeof(quint64)); }
}
}
};
};
......@@ -38,69 +38,69 @@
* **
* ****************************************************************************/
#include <type_traits>
#include <QtCore/qglobal.h>
#define Q_ENABLE_SWAP(Class, Statements) \
Class ( ) = default; \
Class (Class&& o) noexcept { Statements } \
Class& operator=(Class&& o) noexcept { if (this!=&o) { Statements } return *this; }
// //////////////////////////////////////////////////////////////////////////
// Generic fluent interface for traversing and processing structured value types (serialize, deserialize, construct generic in-memory data structures, etc...)
enum BindMode { Read, Write }; //!< drives QBind<TResult,T>() traversal and processing (the design would support others like Append)
template<class T_> class Rec; // a Record data structure (not defined in this Proof-Of-Concept for the sake of simplicity)
template<class T_> class Seq; // a Sequence data structure defined below
template<class T_> class Val // a choice of sequence(), null(), or any value with a textual representation
template<class T_> class Rec; //!< a Record data structure (not defined in this Proof-Of-Concept for the sake of simplicity)
template<class T_> class Seq; //!< a Sequence data structure defined below
template<class T_> class Val //!< a choice of sequence(), null(), or any value with a textual representation
{
Q_DISABLE_COPY(Val) // but not swap
public:
Q_ENABLE_SWAP(Val, std::swap(m_val, o.m_val); std::swap(m_out, o.m_out);)
// T_ can be either a TResult or any combination of nested Seq or Rec like T_ = Rec<Seq<TResult>
using TResult = typename T_::TResult; //!< the start and end point of the traversal as well as the associated processing (see QJsonWriter below for an example)
using TValue = typename TResult::TValue;
static const BindMode Mode = TResult::Mode;
// Enable move semantics as swapping
Val ( ) = default;
Val (Val&& o) noexcept { { std::swap(m_impl, o.m_impl); std::swap(m_out, o.m_out); } }
Val& operator=(Val&& o) noexcept { if (this!=&o) { std::swap(m_impl, o.m_impl); std::swap(m_out, o.m_out); } return *this; }
Val(TValue* impl, T_&& out) { std::swap(m_impl, impl); std::swap(m_out, out); }
Val(TValue val, T_&& out) { std::swap(m_val, val); std::swap(m_out, out); }
operator bool() { return m_impl; } //!< to drive QBind<TResult,T>() traversal
operator bool() { return bool(m_val); } //!< to drive QBind<TResult,T>() traversal
Seq<T_> sequence() { auto impl = Q_LIKELY(m_impl) ? m_impl->sequence() : nullptr; return Seq<T_>(impl, std::move(m_out)); }
/**/T_ null() { if (Q_LIKELY(m_impl) ) m_impl-> null() ; return std::move(m_out) ; }
Seq<T_> sequence() { auto val = Q_LIKELY(m_val) ? m_val->sequence() : nullptr; return Seq<T_>(val, std::move(m_out)); }
/**/T_ null() { if (Q_LIKELY(m_val) ) m_val-> null() ; return std::move(m_out) ; }
template<typename T>
T_ bind(T t) { if (Q_LIKELY(m_impl) ) m_impl-> bind(t) ; return std::move(m_out) ; }
T_ bind(T t) { if (Q_LIKELY(m_val) ) m_val-> bind(t) ; return std::move(m_out) ; }
private:
TValue* m_impl = nullptr; //!< gives semantic to the traversal (nullptr if already moved)
T_ m_out = T_() ; //!< moved context of current traversal up to TResult that will reference the impl itself (be it a QIODevice or QCborValue)
TValue m_val = TValue(); //!< gives semantic to the traversal (nullptr if already moved)
T_ m_out = T_ (); //!< moved context of current traversal up to TResult that will reference the val itself (be it a QIODevice or QCborValue)
};
template<class T_> class Seq
{
Q_DISABLE_COPY(Seq) // but not swap
public:
Q_ENABLE_SWAP(Seq, std::swap(m_seq, o.m_seq); std::swap(m_out, o.m_out);)
using TResult = typename T_::TResult;
using TValue = typename TResult::TValue;
using TSequence = typename TResult::TSequence;
static const BindMode Mode = TResult::Mode;
// Enable move semantics as swapping
Seq ( ) = default;
Seq (Seq&& o) noexcept { { std::swap(m_impl, o.m_impl); std::swap(m_out, o.m_out); } }
Seq& operator=(Seq&& o) noexcept { if (this!=&o) { std::swap(m_impl, o.m_impl); std::swap(m_out, o.m_out); } return *this; }
static const BindMode Mode = TResult::Mode;
Seq(TSequence* impl, T_&& out) { std::swap(m_impl, impl); std::swap(m_out, out); }
Seq(TSequence seq, T_&& out) { std::swap(m_seq, seq); std::swap(m_out, out); }
operator bool() { return m_impl; } //!< to drive QBind<TResult,T>() traversal
operator bool() { return m_seq; } //!< to drive QBind<TResult,T>() traversal
Val<Seq<T_>> item() { auto impl = Q_LIKELY(m_impl) ? m_impl->item() : nullptr; return Val<Seq<T_>>(impl, std::move(*this)); }
T_ out() { if (Q_LIKELY(m_impl) ) m_impl->out () ; return std::move(m_out) ; }
Val<Seq<T_>> item() { auto seq = Q_LIKELY(m_seq) ? m_seq->item() : nullptr; return Val<Seq<T_>>(seq, std::move(*this)); }
T_ out() { if (Q_LIKELY(m_seq) ) m_seq->out () ; return std::move(m_out) ; }
operator TResult() { return out(); /* recursively calls operator TResult() as mandated by T_ */ }
TResult result() { return operator TResult(); }
// Shortcuts
template<typename T> Seq<T_> bind(T t) { return item().bind(t); }
private:
TSequence* m_impl = nullptr;
T_ m_out = T_();
TSequence m_seq = TSequence();
T_ m_out = T_ ();
};
// //////////////////////////////////////////////////////////////////////////
......@@ -113,15 +113,54 @@ struct QBind {
}
};
// T partial specializations generic on TResult (not always on TResult::Mode)
#include <QtCore/qvector.h>
template<class TResult>
struct QBind<TResult, QString> { static TResult bind(Val<TResult> value, QString s) {
static_assert(TResult::Mode==Write,"not implemented");
return value.bind(s.toUtf8().constData());
}};
template<class TResult, typename T>
struct QBind<TResult, QVector<T>> { static TResult bind(Val<TResult> value, QVector<T> vector) {
static_assert(TResult::Mode==Write,"not implemented");
auto s = value.sequence();
for (auto&& d : vector) {
s = s.bind(d);
}
return s;
}};
// //////////////////////////////////////////////////////////////////////////
// TResult example implementation
#include <QtCore/qstack.h>
#include <type_traits>
#include <QtCore/qiodevice.h>
#include <QtCore/qstack.h>
class QJsonWriter
{
class Impl;
Q_DISABLE_COPY(QJsonWriter) // but not swap
public:
Q_ENABLE_SWAP(QJsonWriter, std::swap(m, o.m);)
using TResult = QJsonWriter;
using TValue = QJsonWriter::Impl*;
using TSequence = QJsonWriter::Impl*;
static const BindMode Mode = BindMode::Write;
QJsonWriter(QIODevice* io) : m(new Impl(io)) {}
~QJsonWriter() { if (m) delete m; }
Val<QJsonWriter> begin() { auto beforeMove = m; return std::move(Val<QJsonWriter>(beforeMove, std::move(*this))); }
// Shortcuts
Seq<QJsonWriter> sequence() { return begin().sequence(); }
private:
Impl* m = nullptr;
class Impl {
private:
......@@ -137,18 +176,22 @@ class QJsonWriter
protected:
// static "interface" implementation
template<class T_> friend class Val;
Impl* sequence() { levels.push( Level{"","]"}); return this; }
void null() { io->write( "null"); }
void bind( qlonglong n) { io->write( QByteArray::number(n)); }
void bind( qulonglong n) { io->write( QByteArray::number(n)); }
void bind( double n) { io->write( QByteArray::number(n)); }
void bind(const char* s) { io->write( "\"");
io->write( s ); // TODO escape
io->write( "\""); }
template<typename T> void bind(T t, std::enable_if_t<std::is_floating_point<T>::value >* = nullptr) { bind(( double)t); }
template<typename T> void bind(T t, std::enable_if_t<std::is_integral<T>::value && std::is_signed <T>::value>* = nullptr) { bind(( qlonglong)t); }
template<typename T> void bind(T t, std::enable_if_t<std::is_integral<T>::value && std::is_unsigned<T>::value>* = nullptr) { bind((qulonglong)t); }
Impl* sequence() { levels.push(Level{"","]"});
io->write( "[" ); return this; }
void null() { io->write( "null" ); }
void bind(const char* s) { io->write( "\"" );
io->write( s ); // TODO escape
io->write( "\"" ); }
// Natively supported overloads
void bind( float n) { io->write(QByteArray::number(n, 'g', std::numeric_limits< float>::max_digits10)); }
void bind( double n) { io->write(QByteArray::number(n, 'g', std::numeric_limits<double>::max_digits10)); }
void bind(qulonglong n) { io->write(QByteArray::number(n)); }
void bind( qlonglong n) { io->write(QByteArray::number(n)); }
// This dispatch would be more simple with C++17 constexpr if
template<typename T> void bind(T t, std::enable_if_t<std::is_integral<T>::value && std::is_unsigned<T>::value>* = nullptr) { bind(qulonglong(t)); }
template<typename T> void bind(T t, std::enable_if_t<std::is_integral<T>::value && std:: is_signed<T>::value>* = nullptr) { bind( qlonglong(t)); }
template<typename T> void bind(T t) {
QBind<TResult,T>::bind(Val<TResult>(this, TResult()), t); // Val<TResult> prevents QBind<TResult,T>() from getting out() of an outer Seq for instance
}
......@@ -158,69 +201,8 @@ class QJsonWriter
Impl* item() { io->write(levels.last().sep); levels.last().sep = ","; return this; }
Impl* out() { io->write(levels.pop() .end) ; return this; }
};
public:
using TResult = QJsonWriter;
using TValue = QJsonWriter::Impl;
using TSequence = QJsonWriter::Impl;
static const BindMode Mode = BindMode::Write;
template<typename T>
struct IsSupported : std::enable_if_t<std::is_arithmetic<T>::value> {}; //!< to handle T as Json number
// Enable move semantic as swapping
QJsonWriter ( ) = default;
QJsonWriter (QJsonWriter&& o) noexcept { { std::swap(m, o.m); } }
QJsonWriter& operator=(QJsonWriter&& o) noexcept { if (this!=&o) { std::swap(m, o.m); } return *this; }
QJsonWriter(QIODevice* io) : m(new Impl(io)) {}
~QJsonWriter() { if (m) delete m; }
Val<QJsonWriter> begin() { auto beforeMove = m; return std::move(Val<QJsonWriter>(beforeMove, std::move(*this))); }
private:
Impl* m = nullptr;
};
// //////////////////////////////////////////////////////////////////////////
// QBind<TResult,T> example specializations
#include <QtCore/qvector.h>
#include <QtCore/qmap.h>
template<class TResult>
struct QBind<TResult, QString> { static TResult bind(Val<TResult> value, QString s) {
static_assert(TResult::Mode==Write,"not implemented");
return value.bind(s.toUtf8().constData());
}};
template<class TResult>
struct QBind<TResult, bool> { static TResult bind(Val<TResult> value, bool b) {
static_assert(TResult::Mode==Write,"impossible to read into a bool rvalue");
return value.bind(b ? "TRUE" : "FALSE");
}};
template<class TResult>
struct QBind<TResult, double> { static TResult bind(Val<TResult> value, double d) {
static_assert(TResult::Mode==Write,"impossible to read into a double rvalue");
return value.bind(QByteArray::number(d).constData());
}};
template<class TResult, typename T>
struct QBind<TResult, QVector<T>> { static TResult bind(Val<TResult> value, QVector<T> vector) {
auto s = value.sequence();
for (T& d : vector) {
s = s.item().bind(d);
}
return s;
}};
template<class TResult, typename T, size_t Size>
struct QBind<TResult, T[Size]> { static TResult bind(Val<TResult> value, T(&array)[Size]) {
auto s = value.sequence();
for (T& t : array) {
s = s.item().bind(t);
}
return s;
}};
// //////////////////////////////////////////////////////////////////////////
// Tests and Benchmarks
// NB: It is not possible to use QBENCHMARK to item qDebug because it installs a specific handler
......@@ -230,6 +212,8 @@ struct QBind<TResult, T[Size]> { static TResult bind(Val<TResult> value, T(&arra
#include <QtCore/qbuffer.h>
#include <QtCore/qdebug.h>
#include "QCborWriter.hpp"
#define GROUP(group) { \
qint64 previousTotal=0, groupTotal=0; \
bool groupWarmup; \
......@@ -269,14 +253,10 @@ int main()
// Read-only args
QString text("..ascii characters + U+A4 ¤ U+B0 ° U+D8 Ø U+FF ÿ..");
QMap<char,int> map;
Q_FOREACH(char c, QByteArray("123456789")) {
map[c] = c-'0';
}
QVector<double> transform({1./3, 2./3, 1./3, 1.,
2./3, 1./3, 2./3, 1.,
1./3, 2./3, 1./3, 1.,
0. , 0. , 0. , 1.});
2./3, 1./3, 2./3, 1.,
1./3, 2./3, 1./3, 1.,
0. , 0. , 0. , 1.});
// Temporary buffers
QString s;
......@@ -297,35 +277,25 @@ int main()
STOP("QDebug",s);
START {
b.seek(0); b.buffer().clear();
QJsonWriter w(&b);
w.begin().sequence()
.item().bind(1./3)
.item().bind(text)
;
QJsonWriter(&b)
.sequence()
.bind(1.333333333333f)
.bind(text)
;
}
STOP("Json",QString::fromUtf8(b.buffer()));
START {
b.seek(0); b.buffer().clear();
QCborWriter(&b)
.sequence()
.bind(1.333333333333f)
.bind(text)
;
}
STOP("Cbor",QString::fromUtf8(b.buffer().toHex()));
}
GROUP_STOP;
// GROUP("Format << map")
// {
// START {
// s.clear();
// QDebug(&s)
// << map
// ;
// }
// STOP("QDebug",s);
// START {
// b.seek(0); b.buffer().clear();
// QJsonWriter data(&b);
// data.begin()
// .bind(map)
// ;
// }
// STOP("Json",QString::fromUtf8(b.buffer()));
// }
// GROUP_STOP;
GROUP("Format << floats")
GROUP("Format << doubles")
{
START {
s.clear();
......@@ -338,11 +308,16 @@ int main()
STOP("QDebug",s);
START {
b.seek(0); b.buffer().clear();
QJsonWriter data(&b);
data.begin()
QJsonWriter(&b).begin()
.bind(transform);
}
STOP("Json",b.buffer())
START {
b.seek(0); b.buffer().clear();
QCborWriter(&b).begin()
.bind(transform);
}
STOP("Cbor",QString::fromUtf8(b.buffer().toHex()));
}
GROUP_STOP
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment