/** \example multithreaded.cpp * Using JKQTBasePlotter in multiple threads in parallel. * * \ref JKQTPlotterMultiThreaded */ #include <QApplication> #include <QMainWindow> #include <QLabel> #include <QBoxLayout> #include <QVector> #include <QElapsedTimer> #include <QFile> #include <thread> #include "multithreaded_thread.h" #include "jkqtmath/jkqtpstatbasics.h" #include "jkqtpexampleapplication.h" #include "jkqtplotter_version.h" #define NUM_SHOWN_PLOTS 3 #define NUM_PLOTS 8 #define NUM_GRAPHS 6 #define NUM_DATAPOINTS 1000 #define NUM_REPEATS 3 int main(int argc, char* argv[]) { JKQTPAppSettingController highDPIController(argc,argv); JKQTPExampleApplication app(argc, argv); QMainWindow* mainWin=new QMainWindow(); mainWin->setWindowTitle("Multi-Threaded Plotting"); QWidget* main; mainWin->setCentralWidget(main=new QWidget(mainWin)); QString markdownFile=""; QString labelTemplate="Plot %1: $f(x)=\\cos(x+%1\\pi/8)$"; QByteArray mdMATCH="RESULTS"; for (int i=1; i<argc; i++) { if (QString(argv[i]).startsWith("--mdfile=")) { markdownFile=QString::fromLatin1(argv[i]).right(QString::fromLatin1(argv[i]).size()-9); if (markdownFile.startsWith('"')) markdownFile.remove('"'); } if (QString(argv[i]).startsWith("--complexlabel")) { labelTemplate="Plot %1: $f(x)=\\cos\\left(x+\\sfrac{%1\\pi}{8}\\right)$"; mdMATCH="COMPLEXRESULTS"; } if (QString(argv[i]).startsWith("--simplelabel")) { labelTemplate="Plot %1: $f(x)=\\cos(x+%1\\pi/8)$"; mdMATCH="COMPLEXRESULTS"; } } int result=0; QStringList filenamesSerial, filenamesParallel; { QHBoxLayout* mainLayout=new QHBoxLayout(main); main->setLayout(mainLayout); QVBoxLayout* lay_serial=new QVBoxLayout(); QVBoxLayout* lay_parallel=new QVBoxLayout(); mainLayout->addLayout(lay_serial); QLabel* l; lay_serial->addWidget(l=new QLabel("Serialized Plotting")); QFont f=l->font(); f.setBold(true); f.setPointSize(16); l->setFont(f); lay_parallel->addWidget(l=new QLabel("Parallel Plotting")); l->setFont(f); QLabel* labSerialResult=new QLabel(main); lay_serial->addWidget(labSerialResult); QLabel* labParallelResult=new QLabel(main); lay_parallel->addWidget(labParallelResult); mainLayout->addLayout(lay_parallel); QVector<QLabel*> pic_parallel, pic_serial; for (int i=0; i<NUM_SHOWN_PLOTS; i++) { pic_serial.push_back(new QLabel(main)); pic_parallel.push_back(new QLabel(main)); lay_serial->addWidget(pic_serial.last(), 1); lay_parallel->addWidget(pic_parallel.last(), 1); } QList<double> runtimesParallel; QList<double> runtimesSerial; double durSerialNano=0; double durParallelNano=0; for (int run=0; run<NUM_REPEATS; run++) { filenamesParallel.clear(); QElapsedTimer timer; timer.start(); for (int i=0; i<NUM_PLOTS; i++) { double dur=0; filenamesSerial<<PlottingThread::plotAndSave("serial", i, NUM_GRAPHS, NUM_DATAPOINTS, labelTemplate, &dur); runtimesSerial<<dur/1e6; } durSerialNano+=timer.nsecsElapsed(); qDebug()<<"durSerial = "<<durSerialNano/1e6<<"ms"; QList<QSharedPointer<PlottingThread>> threads; for (int i=0; i<NUM_PLOTS; i++) { qDebug()<<" creating thread "<<i; threads.append(QSharedPointer<PlottingThread>::create("parallel",i, NUM_GRAPHS, NUM_DATAPOINTS, labelTemplate, nullptr)); } timer.start(); for (int i=0; i<NUM_PLOTS; i++) { qDebug()<<" staring thread "<<i; threads[i]->start(); } for (int i=0; i<NUM_PLOTS; i++) { qDebug()<<" waiting for thread "<<i; if (threads[i]->wait()) { filenamesParallel<<threads[i]->getFilename(); runtimesParallel<<threads[i]->getRuntimeNanosends()/1e6; } } durParallelNano+=timer.nsecsElapsed(); qDebug()<<"durParallel["<<run+1<<"] = "<<durParallelNano/1e6<<"ms"; threads.clear(); } for (int ii=0; ii<NUM_SHOWN_PLOTS; ii++) { int i=ii; if (ii>NUM_SHOWN_PLOTS/2) i=NUM_PLOTS-1-NUM_SHOWN_PLOTS+ii; pic_serial[ii]->setPixmap(QPixmap(filenamesSerial[i], "PNG")); pic_parallel[ii]->setPixmap(QPixmap(filenamesParallel[i], "PNG")); } QString ser_result, par_result; labSerialResult->setText(ser_result=QString("runtime, overall = %1ms<br/>single runtimes = (%2 +/- %3) ms<br/>speedup = %4x<br/>threads / available = %5 / %6<br/><br/><br/> ").arg(durSerialNano/1e6,0,'f',1).arg(jkqtpstatAverage(runtimesSerial.begin(), runtimesSerial.end()),0,'f',1).arg(jkqtpstatStdDev(runtimesSerial.begin(), runtimesSerial.end()),0,'f',1).arg(jkqtpstatSum(runtimesSerial.begin(), runtimesSerial.end())/(durSerialNano/1e6),0,'f',2).arg(1).arg(std::thread::hardware_concurrency())); labParallelResult->setText(par_result=QString("runtime, overall = %1ms<br/>single runtimes = (%2 +/- %3) ms<br/>speedup = %4x<br/>threads / available = %5 / %6<br/>batch runs = %8<br/><br/><b>speedup vs. serial = %7x</b>").arg(durParallelNano/1e6,0,'f',1).arg(jkqtpstatAverage(runtimesParallel.begin(), runtimesParallel.end()),0,'f',1).arg(jkqtpstatStdDev(runtimesParallel.begin(), runtimesParallel.end()),0,'f',1).arg(jkqtpstatSum(runtimesParallel.begin(), runtimesParallel.end())/(durParallelNano/1e6),0,'f',2).arg(NUM_PLOTS).arg(std::thread::hardware_concurrency()).arg(durSerialNano/durParallelNano,0,'f',1).arg(NUM_REPEATS)); mainWin->show(); if (!markdownFile.isEmpty()) { qDebug()<<"modifying MD-file "<<markdownFile; QFile f(markdownFile); QByteArray md; if (f.open(QFile::ReadOnly|QFile::Text)) { md=f.readAll(); qDebug()<<" read "<<md.size()<<" bytes"; f.close(); const auto istart=md.indexOf("[comment]:"+mdMATCH); const auto iend=md.indexOf("[comment]:"+mdMATCH+"_END"); qDebug()<<" istart="<<istart<<", iend="<<iend; if (istart>=0 && iend>istart) { const QByteArray newResults="[comment]:"+mdMATCH+"\n\n<b>VERSION:</b> "+QByteArray(JKQTPLOTTER_VERSION::PROJECT_VERSION) +"\n<b>BUILD MODE:</b> "+QByteArray(JKQTPLOTTER_VERSION::PROJECT_BUILDTYPE) +"\n\n<u><b>SERIAL RESULTS:</b></u><br/>"+ser_result.toUtf8() +"\n\n<u><b>PARALLEL RESULTS:</b></u><br/>\n"+par_result.toUtf8()+"\n\n"; md.replace(istart,iend-istart,newResults); if (f.open(QFile::WriteOnly)) { qDebug()<<" writing "<<md.size() <<"bytes"; f.write(md); f.close(); } } } } else { qDebug()<<"no MD-file given"; } result = app.exec(); } for (const auto& fn: (filenamesSerial+filenamesParallel)) { QFile::remove(fn); } return result; }