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

Qt POC with same write perf as QDebug

parent 9502abd5
#-------------------------------------------------
#
# Project created by QtCreator 2018-06-12T22:10:15
#
#-------------------------------------------------
QT -= gui
TARGET = QBind
CONFIG += console c++1z
CONFIG -= app_bundle
TEMPLATE = app
# The following define makes your compiler emit warnings if you use
# any feature of Qt which has been marked as deprecated (the exact warnings
# depend on your compiler). Please consult the documentation of the
# deprecated API in order to know how to port your code away from it.
DEFINES += QT_DEPRECATED_WARNINGS
# You can also make your code fail to compile if you use deprecated APIs.
# In order to do so, uncomment the following line.
# You can also select to disable deprecated APIs only up to a certain version of Qt.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
QMAKE_CXXFLAGS_RELEASE += /Zi
QMAKE_LFLAGS_RELEASE += /DEBUG
SOURCES += \
main.cpp
/****************************************************************************
* **
* ** Copyright (C) 2016 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$
* **
* ****************************************************************************/
#include <type_traits>
#include <QtCore/qglobal.h>
// //////////////////////////////////////////////////////////////////////////
// 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
{
Q_DISABLE_COPY(Val) // but not swap
public:
// 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); }
operator bool() { return m_impl; } //!< 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) ; }
template<typename T>
T_ bind(T t) { if (Q_LIKELY(m_impl) ) m_impl-> 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)
};
template<class T_> class Seq
{
Q_DISABLE_COPY(Seq) // but not swap
public:
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; }
Seq(TSequence* impl, T_&& out) { std::swap(m_impl, impl); std::swap(m_out, out); }
operator bool() { return m_impl; } //!< 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) ; }
operator TResult() { return out(); /* recursively calls operator TResult() as mandated by T_ */ }
TResult result() { return operator TResult(); }
private:
TSequence* m_impl = nullptr;
T_ m_out = T_();
};
// //////////////////////////////////////////////////////////////////////////
// QBind<TResult,T>
template<class TResult, typename T>
struct QBind {
static TResult bind(Val<TResult> value, T t) {
return t.bind(value); // In case of error, define a T::bind(Val<TResult>) method or an external QBind<TResult,T>::bind(Val<TResult>,T)
}
};
// //////////////////////////////////////////////////////////////////////////
// TResult example implementation
#include <QtCore/qstack.h>
#include <QtCore/qiodevice.h>
class QJsonWriter
{
Q_DISABLE_COPY(QJsonWriter) // but not swap
class Impl {
private:
struct Level {
const char* sep;
const char* end;
};
QIODevice* io = nullptr;
QStack<Level> levels = QStack<Level>(); //!< minimal dynamic context to implement out() and ensure well-formedness in case TResult is abandoned
public:
Impl(QIODevice* io) : io(io) {}
~Impl() { for (auto&& level : levels) io->write(level.end); }
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); }
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() { 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
#include <QtCore/qelapsedtimer.h>
#include <QtCore/qfile.h>
#include <QtCore/qbuffer.h>
#include <QtCore/qdebug.h>
#define GROUP(group) { \
qint64 previousTotal=0, groupTotal=0; \
bool groupWarmup; \
do { \
groupWarmup=(previousTotal==0 || abs(groupTotal-previousTotal)*100/previousTotal > 1); \
previousTotal=groupTotal; \
groupTotal=0; \
if (!previousTotal) { fprintf(results,"group"); fprintf(samples, "=== " group " ========================================\n"); } else fprintf(results,group);
#define GROUP_STOP \
if (!previousTotal) fprintf(results,",total(usecs),variation(%%)\n"); else fprintf(results,",%10lld,%3lld\n", groupTotal, previousTotal ? abs(groupTotal-previousTotal)*100/previousTotal : 0); \
} while (groupWarmup); }
QElapsedTimer item;
#define START item.start(); for (int i=0; i<123; ++i)
#define STOP(label,result) { \
auto usecs=item.nsecsElapsed()/1000/123; groupTotal+=usecs; \
if (!previousTotal) { fprintf(results,"," label); fprintf(samples, "--- " label " ---------------------------------------\n%s\n", qPrintable(result)); } else fprintf(results,",%10lld", usecs); }
int main()
{
// Generated files
FILE* results = fopen("results.csv", "w");
FILE* samples = fopen("samples.txt", "w");
if (!results) return -1;
if (!samples) return -1;
QFile json( "modmed.json");
QFile xml ( "modmed.xml" );
QFile fjson("fmodmed.json");
QFile fxml ("fmodmed.xml" );
if (! json.open(QIODevice::ReadWrite|QIODevice::Truncate)) return -1;
if (! xml .open(QIODevice::ReadWrite|QIODevice::Truncate)) return -1;
if (!fjson.open(QIODevice::ReadWrite|QIODevice::Truncate)) return -1;
if (!fxml .open(QIODevice::ReadWrite|QIODevice::Truncate)) return -1;
// 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.});
// Temporary buffers
QString s;
QBuffer b;
b.open(QIODevice::WriteOnly);
GROUP("Format << atoms")
{
START {
s.clear();
QDebug(&s)
<< '['
<< ',' << 1.333333333333f
<< ',' << text
<< ']'
;
}
STOP("QDebug",s);
START {
b.seek(0); b.buffer().clear();
QJsonWriter w(&b);
w.begin().sequence()
.item().bind(1./3)
.item().bind(text)
;
}
STOP("Json",QString::fromUtf8(b.buffer()));
}
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")
{
START {
s.clear();
QDebug data(&s);
data << '[';
for (int i=0; i < 16; i++) {
data << transform[i] << '|';
};
}
STOP("QDebug",s);
START {
b.seek(0); b.buffer().clear();
QJsonWriter data(&b);
data.begin()
.bind(transform);
}
STOP("Json",b.buffer())
}
GROUP_STOP
return fclose(samples);
return fclose(results);
}
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