mirror of
https://github.com/jkriege2/JKQtPlotter.git
synced 2024-12-25 18:11:38 +08:00
NEW/REWORKED JKQTBasePlottercan be used is re-entrant, i.e. different instances can be used from different threads in parallel (although there is significant overhead due to shared caches between the threads!).
NEW added multithreaded example to demonstrate using JKQTBasePlotter in several parallel threads
This commit is contained in:
parent
3b598f49e8
commit
aa2fcb108d
@ -238,6 +238,7 @@ if(JKQtPlotter_BUILD_EXAMPLES)
|
|||||||
datastore_regression/datastore_regression,datastore_regression_lin,datastore_regression_linrobust,datastore_regression_linrobust_p,datastore_regression_linweight,datastore_regression_nonlinreg_exp,datastore_regression_nonlinreg_pow,datastore_regression_polynom,datastore_regression_polynom_errros/--iteratefunctorsteps
|
datastore_regression/datastore_regression,datastore_regression_lin,datastore_regression_linrobust,datastore_regression_linrobust_p,datastore_regression_linweight,datastore_regression_nonlinreg_exp,datastore_regression_nonlinreg_pow,datastore_regression_polynom,datastore_regression_polynom_errros/--iteratefunctorsteps
|
||||||
datastore_statistics/datastore_statistics,datastore_statistics_dataonly,datastore_statistics_boxplots_simple,datastore_statistics_boxplots_outliers,datastore_statistics_hist,datastore_statistics_kde,datastore_statistics_cumhistkde/--iteratefunctorsteps
|
datastore_statistics/datastore_statistics,datastore_statistics_dataonly,datastore_statistics_boxplots_simple,datastore_statistics_boxplots_outliers,datastore_statistics_hist,datastore_statistics_kde,datastore_statistics_cumhistkde/--iteratefunctorsteps
|
||||||
datastore_statistics_2d/datastore_statistics_2d
|
datastore_statistics_2d/datastore_statistics_2d
|
||||||
|
multithreaded/multithreaded/--mdfile=${CMAKE_CURRENT_LIST_DIR}/../examples/multithreaded/README.md
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -279,7 +280,7 @@ if(JKQtPlotter_BUILD_EXAMPLES)
|
|||||||
foreach(ex ${JKQTPlotter_GenerateDocScreenshots_From})
|
foreach(ex ${JKQTPlotter_GenerateDocScreenshots_From})
|
||||||
set(example ${ex})
|
set(example ${ex})
|
||||||
set(basename ${ex})
|
set(basename ${ex})
|
||||||
string(REGEX MATCH "(.+)/(.+)" dummy ${ex})
|
string(REGEX MATCH "([^/]+)/([^/]+)" dummy ${ex})
|
||||||
set(extra_command "")
|
set(extra_command "")
|
||||||
if(CMAKE_MATCH_1 STREQUAL "" OR CMAKE_MATCH_2 STREQUAL "")
|
if(CMAKE_MATCH_1 STREQUAL "" OR CMAKE_MATCH_2 STREQUAL "")
|
||||||
set(example ${ex})
|
set(example ${ex})
|
||||||
@ -288,7 +289,7 @@ if(JKQtPlotter_BUILD_EXAMPLES)
|
|||||||
set(example ${CMAKE_MATCH_1})
|
set(example ${CMAKE_MATCH_1})
|
||||||
set(basename ${CMAKE_MATCH_2})
|
set(basename ${CMAKE_MATCH_2})
|
||||||
set(CMAKE_MATCH_3 "")
|
set(CMAKE_MATCH_3 "")
|
||||||
string(REGEX MATCH "(.+)/(.*)/(.+)" dummy ${ex})
|
string(REGEX MATCH "([^/]+)/([^/]*)/(.+)" dummy ${ex})
|
||||||
if(NOT (CMAKE_MATCH_3 STREQUAL ""))
|
if(NOT (CMAKE_MATCH_3 STREQUAL ""))
|
||||||
set(example ${CMAKE_MATCH_1})
|
set(example ${CMAKE_MATCH_1})
|
||||||
set(basename ${CMAKE_MATCH_2})
|
set(basename ${CMAKE_MATCH_2})
|
||||||
|
@ -127,7 +127,7 @@ All test-projects are Qt-projects that use qmake to build. You can load them int
|
|||||||
<td> `JKQTPXYLineGraph` and `JKQTPFilledVerticalRangeGraph` <br> C++ vector of data <br> date/time axes <br> plot min/max range graph <br> internal LaTeX parser <br> data from CSV files
|
<td> `JKQTPXYLineGraph` and `JKQTPFilledVerticalRangeGraph` <br> C++ vector of data <br> date/time axes <br> plot min/max range graph <br> internal LaTeX parser <br> data from CSV files
|
||||||
<tr><td> \image html second_axis_small.png
|
<tr><td> \image html second_axis_small.png
|
||||||
<td> \subpage JKQTPlotterSecondaryAxes
|
<td> \subpage JKQTPlotterSecondaryAxes
|
||||||
<td> plottig with secondary axes, `JKQTPBasePlotter::addSecondaryXAxis()`/`JKQTPBasePlotter::addSecondaryYAxis()`
|
<td> plottig with secondary axes, `JKQTBasePlotter::addSecondaryXAxis()`/`JKQTBasePlotter::addSecondaryYAxis()`
|
||||||
<tr><td> \image html advancedlineandfillstyling_small.png
|
<tr><td> \image html advancedlineandfillstyling_small.png
|
||||||
<td> \subpage JKQTPlotterAdvancedLineAndFillStyling
|
<td> \subpage JKQTPlotterAdvancedLineAndFillStyling
|
||||||
<td> `JKQTPXYLineGraph`, `JKQTPSpecialLineHorizontalGraph` and `JKQTPBarVerticalGraph` <br> C++ vector of data <br> advanced line styling and filling
|
<td> `JKQTPXYLineGraph`, `JKQTPSpecialLineHorizontalGraph` and `JKQTPBarVerticalGraph` <br> C++ vector of data <br> advanced line styling and filling
|
||||||
@ -252,6 +252,15 @@ All test-projects are Qt-projects that use qmake to build. You can load them int
|
|||||||
<td> Allows to zoom into the Mandelbrot Set, using the different Zooming methods of JKQTPlotter
|
<td> Allows to zoom into the Mandelbrot Set, using the different Zooming methods of JKQTPlotter
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
\subsection jkqtp_extut_specialusecasesexamples Examples for special Use-Cases
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<tr><th> Screenshot <th> Description <th> Notes
|
||||||
|
<tr><td> \image html multithreaded_small.png
|
||||||
|
<td> \subpage JKQTPlotterMultiThreaded
|
||||||
|
<td> multi-threaded plotting using JKQTBasePlotter
|
||||||
|
</table>
|
||||||
|
|
||||||
|
|
||||||
\subsection jkqtp_extut_cmake_build Examples for CMake Build System
|
\subsection jkqtp_extut_cmake_build Examples for CMake Build System
|
||||||
|
|
||||||
|
@ -47,7 +47,11 @@
|
|||||||
With simlar code you can also integrate JKQTBasePlotter into your own widgets.
|
With simlar code you can also integrate JKQTBasePlotter into your own widgets.
|
||||||
|
|
||||||
|
|
||||||
|
This class is immpleented in a such a way that ^different instances can be used in different parallel threads, i.e. the class is re-entrant.
|
||||||
|
There are however access to different cached data is synchronized between all threads (i.e. static internal caches are used), which limmits
|
||||||
|
the acheavable parallelization speedup!
|
||||||
|
|
||||||
|
\see See \ref JKQTPlotterMultiThreaded for an example of multi-threaded plotting!
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -47,6 +47,7 @@ Changes, compared to \ref page_whatsnew_V4_0_0 "v4.0.0" include:
|
|||||||
<li>FIXED issue <a href="https://github.com/jkriege2/JKQtPlotter/pull/99">#99: Clipping of Tick Labels</a>: for horizontal axes, additional space at the left and/or right of the plot is allowed so labels are no longer clipped (thanks to <a href="https://github.com/allenbarnett5">user:allenbarnett5/a> for reporting)</li>
|
<li>FIXED issue <a href="https://github.com/jkriege2/JKQtPlotter/pull/99">#99: Clipping of Tick Labels</a>: for horizontal axes, additional space at the left and/or right of the plot is allowed so labels are no longer clipped (thanks to <a href="https://github.com/allenbarnett5">user:allenbarnett5/a> for reporting)</li>
|
||||||
<li>FIXED issue <a href="https://github.com/jkriege2/JKQtPlotter/pull/99">#99: Height of one-column key/legend was too large</a> (thanks to <a href="https://github.com/allenbarnett5">user:allenbarnett5/a> for reporting)</li>
|
<li>FIXED issue <a href="https://github.com/jkriege2/JKQtPlotter/pull/99">#99: Height of one-column key/legend was too large</a> (thanks to <a href="https://github.com/allenbarnett5">user:allenbarnett5/a> for reporting)</li>
|
||||||
<li>FIXED issue mentioned in <a href="https://github.com/jkriege2/JKQtPlotter/pull/110">#110: Lock the panning action to certain values: View zooms in, when panning close to AbosluteXY</a> (thanks to <a href="https://github.com/sim186">user:sim186</a> for reporting)</li>
|
<li>FIXED issue mentioned in <a href="https://github.com/jkriege2/JKQtPlotter/pull/110">#110: Lock the panning action to certain values: View zooms in, when panning close to AbosluteXY</a> (thanks to <a href="https://github.com/sim186">user:sim186</a> for reporting)</li>
|
||||||
|
<li>FIXED: jkqtpstatSum() and jkqtpstatSumSqr() did not work, as a non-existing function is called internally</li>
|
||||||
<li>FIXED/IMPROVED issue <a href="https://github.com/jkriege2/JKQtPlotter/issues/100">#100: Add option to disable resize delay feature by setting the delay to zero</a> (thanks to <a href="https://github.com/fpalazzolo">user:fpalazzolo</a> for reporting)</li>
|
<li>FIXED/IMPROVED issue <a href="https://github.com/jkriege2/JKQtPlotter/issues/100">#100: Add option to disable resize delay feature by setting the delay to zero</a> (thanks to <a href="https://github.com/fpalazzolo">user:fpalazzolo</a> for reporting)</li>
|
||||||
<li>FIXED/NEW: placement of plot-title (was not centerd in its box, but glued to the bottom) by adding a plotstyle parameter JKQTBasePlotterStyle::plotLabelOffset</li>
|
<li>FIXED/NEW: placement of plot-title (was not centerd in its box, but glued to the bottom) by adding a plotstyle parameter JKQTBasePlotterStyle::plotLabelOffset</li>
|
||||||
<li>FIXED/REWORKED issue <a href="https://github.com/jkriege2/JKQtPlotter/issues/111">#111: Can't write to PDF files with JKQTPlotter::saveImage() when passing a filename ending in ".pdf"</a> (thanks to <a href="https://github.com/fpalazzolo">user:fpalazzolo/a> for reporting):<br/>While fixing this issue, the functions JKQTBasePlotter::saveImage() etc. gained a bool return value to indicate whether sacing was successful.</li>
|
<li>FIXED/REWORKED issue <a href="https://github.com/jkriege2/JKQtPlotter/issues/111">#111: Can't write to PDF files with JKQTPlotter::saveImage() when passing a filename ending in ".pdf"</a> (thanks to <a href="https://github.com/fpalazzolo">user:fpalazzolo/a> for reporting):<br/>While fixing this issue, the functions JKQTBasePlotter::saveImage() etc. gained a bool return value to indicate whether sacing was successful.</li>
|
||||||
@ -108,6 +109,7 @@ Changes, compared to \ref page_whatsnew_V4_0_0 "v4.0.0" include:
|
|||||||
<li>NEW: added JKQTBasePlotterStyle::plotLabelTopBorder to set the spacing between top and plot label</li>
|
<li>NEW: added JKQTBasePlotterStyle::plotLabelTopBorder to set the spacing between top and plot label</li>
|
||||||
<li>NEW: Due to addition of JKQTMathText::setFontOptions() and the matchign extension of JKQTMathText::setFontSpecial() (see below) you can now add modifiers like <tt>+BOLD+ITALIC</tt> to any font-name provided to JKQTPlotter and in style INI-files</li>
|
<li>NEW: Due to addition of JKQTMathText::setFontOptions() and the matchign extension of JKQTMathText::setFontSpecial() (see below) you can now add modifiers like <tt>+BOLD+ITALIC</tt> to any font-name provided to JKQTPlotter and in style INI-files</li>
|
||||||
<li>NEW: added JKQTPLabelMinBesides and JKQTPLabelMaxBesides to JKQTPLabelPosition, so labels can be set besides the axes</li>
|
<li>NEW: added JKQTPLabelMinBesides and JKQTPLabelMaxBesides to JKQTPLabelPosition, so labels can be set besides the axes</li>
|
||||||
|
<li>NEW/REWORKED JKQTBasePlottercan be used is re-entrant, i.e. different instances can be used from different threads in parallel (although there is significant overhead due to shared caches between the threads!). This is demonstrated and discussed in \ref JKQTPlotterMultiThreaded . </li>
|
||||||
</ul></li>
|
</ul></li>
|
||||||
|
|
||||||
<li>JKQTMathText:<ul>
|
<li>JKQTMathText:<ul>
|
||||||
|
@ -80,6 +80,7 @@ add_subdirectory(impulsesplot)
|
|||||||
add_subdirectory(logaxes)
|
add_subdirectory(logaxes)
|
||||||
add_subdirectory(mandelbrot)
|
add_subdirectory(mandelbrot)
|
||||||
add_subdirectory(multiplot)
|
add_subdirectory(multiplot)
|
||||||
|
add_subdirectory(multithreaded)
|
||||||
add_subdirectory(parametriccurve)
|
add_subdirectory(parametriccurve)
|
||||||
add_subdirectory(paramscatterplot)
|
add_subdirectory(paramscatterplot)
|
||||||
add_subdirectory(paramscatterplot_image)
|
add_subdirectory(paramscatterplot_image)
|
||||||
|
33
examples/multithreaded/CMakeLists.txt
Normal file
33
examples/multithreaded/CMakeLists.txt
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.16)
|
||||||
|
|
||||||
|
set(EXAMPLE_NAME multithreaded)
|
||||||
|
set(EXENAME jkqtptest_${EXAMPLE_NAME})
|
||||||
|
|
||||||
|
message( STATUS ".. Building Example ${EXAMPLE_NAME}" )
|
||||||
|
|
||||||
|
|
||||||
|
# Set up source files
|
||||||
|
set(SOURCES multithreaded.cpp )
|
||||||
|
set(HEADERS multithreaded_thread.h )
|
||||||
|
set(RESOURCES )
|
||||||
|
set(UIS )
|
||||||
|
|
||||||
|
add_executable(${EXENAME} WIN32 ${SOURCES} ${HEADERS} ${RESOURCES} ${UIS})
|
||||||
|
target_link_libraries(${EXENAME} JKQTPExampleToolsLib)
|
||||||
|
target_include_directories(${EXENAME} PRIVATE ../../lib)
|
||||||
|
if(JKQtPlotter_BUILD_STATIC_LIBS)
|
||||||
|
target_link_libraries(${EXENAME} JKQTPlotterLib)
|
||||||
|
|
||||||
|
elseif(JKQtPlotter_BUILD_SHARED_LIBS)
|
||||||
|
target_link_libraries(${EXENAME} JKQTPlotterSharedLib)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# precomiled headers to speed up compilation
|
||||||
|
target_precompile_headers(${EXENAME} PRIVATE ../../lib/jkqtplotter/private/jkqtplotter_precomp.h)
|
||||||
|
|
||||||
|
|
||||||
|
# Installation
|
||||||
|
install(TARGETS ${EXENAME} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
|
||||||
|
|
||||||
|
#Installation of Qt DLLs on Windows
|
||||||
|
jkqtplotter_deployqt(${EXENAME})
|
110
examples/multithreaded/README.md
Normal file
110
examples/multithreaded/README.md
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
# Example (JKQTPlotter): Multi-Threaded (Parallel) Plotting {#JKQTPlotterMultiThreaded}
|
||||||
|
This project (see `./examples/multithreaded/`) shows how to use JKQTBasePlotter in multiple threads in parallel.
|
||||||
|
|
||||||
|
The source code of the main application can be found in [`multithreaded.cpp`](https://github.com/jkriege2/JKQtPlotter/tree/master/examples/multithreaded/multithreaded.cpp) and [`multithreaded_thread.cpp`](https://github.com/jkriege2/JKQtPlotter/tree/master/examples/multithreaded/multithreaded_thread.cpp).
|
||||||
|
|
||||||
|
The file [`multithreaded_thread.cpp`](https://github.com/jkriege2/JKQtPlotter/tree/master/examples/multithreaded/multithreaded_thread.cpp) contains a [`QThread`](https://doc.qt.io/qt-6/qthread.html) class that implements the actual plotting within a static method that is also run inside the thread's [`QThread::run()`](https://doc.qt.io/qt-6/qthread.html#run) method. It generates a plot with several line-graphs and then saves them into a PNG-file:
|
||||||
|
|
||||||
|
```.cpp
|
||||||
|
public:
|
||||||
|
inline static QString plotAndSave(const QString& filenamepart, int plotIndex, int NUM_GRAPHS, int NUM_DATAPOINTS, double* runtimeNanoseconds=nullptr) {
|
||||||
|
QElapsedTimer timer;
|
||||||
|
timer.start();
|
||||||
|
const QString filename=QDir(QDir::tempPath()).absoluteFilePath(QString("testimg_%1_%2.png").arg(filenamepart).arg(plotIndex));
|
||||||
|
JKQTBasePlotter plot(true);
|
||||||
|
|
||||||
|
const size_t colX=plot.getDatastore()->addLinearColumn(NUM_DATAPOINTS, 0, 10, "x");
|
||||||
|
QRandomGenerator rng;
|
||||||
|
for (int i=0; i<NUM_GRAPHS; i++) {
|
||||||
|
JKQTPXYLineGraph* g;
|
||||||
|
plot.addGraph(g=new JKQTPXYLineGraph(&plot));
|
||||||
|
g->setXColumn(colX);
|
||||||
|
g->setYColumn(plot.getDatastore()->addColumnCalculatedFromColumn(colX, [&](double x) { return cos(x+double(i)/8.0*JKQTPSTATISTICS_PI)+rng.generateDouble()*0.2-0.1;}));
|
||||||
|
g->setTitle(QString("Plot %1: $f(x)=\\cos\\leftx+\\frac{%1\\pi}{8}\\right)").arg(i+1));
|
||||||
|
g->setDrawLine(true);
|
||||||
|
g->setSymbolType(JKQTPNoSymbol);
|
||||||
|
|
||||||
|
}
|
||||||
|
plot.setPlotLabel(QString("Test Plot %1").arg(plotIndex+1));
|
||||||
|
plot.getXAxis()->setAxisLabel("x-axis");
|
||||||
|
plot.getYAxis()->setAxisLabel("y-axis");
|
||||||
|
plot.zoomToFit();
|
||||||
|
plot.saveAsPixelImage(filename, false, "PNG");
|
||||||
|
|
||||||
|
if (runtimeNanoseconds) *runtimeNanoseconds=timer.nsecsElapsed();
|
||||||
|
return filename;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ...
|
||||||
|
|
||||||
|
protected:
|
||||||
|
inline virtual void run() {
|
||||||
|
m_filename=plotAndSave(m_filenamepart, m_plotindex, m_NUM_GRAPHS, m_NUM_DATAPOINTS, &m_runtimeNanoseconds);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The main application in [`multithreaded.cpp`](https://github.com/jkriege2/JKQtPlotter/tree/master/examples/multithreaded/multithreaded.cpp) then uses this method/thread-class to perform a test: First the function is run several times serially and then an equal amount of times in parallel.
|
||||||
|
|
||||||
|
```.cpp
|
||||||
|
#define NUM_PLOTS 8
|
||||||
|
#define NUM_GRAPHS 6
|
||||||
|
#define NUM_DATAPOINTS 1000
|
||||||
|
|
||||||
|
QElapsedTimer timer;
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// serial plotting
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////
|
||||||
|
timer.start();
|
||||||
|
for (int i=0; i<NUM_PLOTS; i++) {
|
||||||
|
PlottingThread::plotAndSave("serial", i, NUM_GRAPHS, NUM_DATAPOINTS);
|
||||||
|
}
|
||||||
|
const double durSerialNano=timer.nsecsElapsed();
|
||||||
|
qDebug()<<"durSerial = "<<durSerialNano/1e6<<"ms";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// parallel plotting
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////
|
||||||
|
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, 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;
|
||||||
|
threads[i]->wait();
|
||||||
|
}
|
||||||
|
const double durParallelNano=timer.nsecsElapsed();
|
||||||
|
qDebug()<<"durParallel = "<<durParallelNano/1e6<<"ms";
|
||||||
|
|
||||||
|
threads.clear();
|
||||||
|
```
|
||||||
|
|
||||||
|
This test results in the following numbers (on my AMD Ryzen5 8/16-core laptop):
|
||||||
|
|
||||||
|
[comment]:RESULTS
|
||||||
|
|
||||||
|
<u><b>SERIAL RESULTS:</b></u><br/>runtime, overall = 1719.3ms<br/>single runtimes = (214.8 +/- 277.4) ms<br/>speedup = 1.00x<br/>threads / available = 1 / 16<br/><br/>
|
||||||
|
|
||||||
|
<u><b>PARALLEL RESULTS:</b></u><br/>
|
||||||
|
runtime, overall = 649.1ms<br/>single runtimes = (605.2 +/- 81.8) ms<br/>speedup = 7.46x<br/>threads / available = 8 / 16<br/><br/><b>speedup vs. serial = 2.6x</b>
|
||||||
|
|
||||||
|
[comment]:RESULTS_END
|
||||||
|
|
||||||
|
From this data you can observe:
|
||||||
|
- The plotting parallelizes nicely, i.e. the speedup ist >7x on a 8-core-machine. This is the speedup calculated as sum of runtimes of each thread, divided by the runtime of all threads in parallel.
|
||||||
|
- BUT: the speedup of serialized plotting vs. parallel plotting is way smaller: It is only 2-3x. This can be explained by the (significant) overhead due to shared caches (and therefore synchronization) between the plotters. This may be reworked in future!
|
||||||
|
- The variance in runtimes in the (initial) serial test-run is larger than in the parallel run. This is due to filling of the internal caches during the first plotting!
|
||||||
|
.
|
||||||
|
|
||||||
|
Finally the application displays the plots:
|
||||||
|
|
||||||
|
![multithreaded](https://raw.githubusercontent.com/jkriege2/JKQtPlotter/master/screenshots/multithreaded.png)
|
||||||
|
|
152
examples/multithreaded/multithreaded.cpp
Normal file
152
examples/multithreaded/multithreaded.cpp
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
/** \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"
|
||||||
|
|
||||||
|
#define NUM_SHOWN_PLOTS 3
|
||||||
|
#define NUM_PLOTS 8
|
||||||
|
#define NUM_GRAPHS 6
|
||||||
|
#define NUM_DATAPOINTS 1000
|
||||||
|
|
||||||
|
|
||||||
|
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="";
|
||||||
|
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('"');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
QElapsedTimer timer;
|
||||||
|
QList<double> runtimesSerial;
|
||||||
|
timer.start();
|
||||||
|
for (int i=0; i<NUM_PLOTS; i++) {
|
||||||
|
double dur=0;
|
||||||
|
filenamesSerial<<PlottingThread::plotAndSave("serial", i, NUM_GRAPHS, NUM_DATAPOINTS, &dur);
|
||||||
|
runtimesSerial<<dur/1e6;
|
||||||
|
}
|
||||||
|
const double durSerialNano=timer.nsecsElapsed();
|
||||||
|
qDebug()<<"durSerial = "<<durSerialNano/1e6<<"ms";
|
||||||
|
|
||||||
|
QList<double> runtimesParallel;
|
||||||
|
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, 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const double durParallelNano=timer.nsecsElapsed();
|
||||||
|
qDebug()<<"durParallel = "<<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/> ").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/><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));
|
||||||
|
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]:RESULTS");
|
||||||
|
const auto iend=md.indexOf("[comment]:RESULTS_END");
|
||||||
|
qDebug()<<" istart="<<istart<<", iend="<<iend;
|
||||||
|
if (istart>=0 && iend>istart) {
|
||||||
|
const QByteArray newResults="[comment]:RESULTS\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;
|
||||||
|
}
|
73
examples/multithreaded/multithreaded_thread.h
Normal file
73
examples/multithreaded/multithreaded_thread.h
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
#ifndef MULTITHREADED_THREAD_H
|
||||||
|
#define MULTITHREADED_THREAD_H
|
||||||
|
|
||||||
|
#include <QThread>
|
||||||
|
#include "jkqtplotter/jkqtpbaseplotter.h"
|
||||||
|
#include "jkqtplotter/graphs/jkqtplines.h"
|
||||||
|
#include "jkqtcommon/jkqtpmathtools.h"
|
||||||
|
#include <QElapsedTimer>
|
||||||
|
#include <QRandomGenerator>
|
||||||
|
#include <QObject>
|
||||||
|
#include <QDir>
|
||||||
|
#include <QString>
|
||||||
|
|
||||||
|
class PlottingThread: public QThread {
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
inline PlottingThread(const QString& filenamepart, int plotindex, int NUM_GRAPHS, int NUM_DATAPOINTS, QObject* parent):
|
||||||
|
QThread(parent),
|
||||||
|
m_plotindex(plotindex),
|
||||||
|
m_runtimeNanoseconds(0),
|
||||||
|
m_filenamepart(filenamepart),
|
||||||
|
m_filename(),
|
||||||
|
m_NUM_GRAPHS(NUM_GRAPHS),
|
||||||
|
m_NUM_DATAPOINTS(NUM_DATAPOINTS)
|
||||||
|
{}
|
||||||
|
|
||||||
|
inline static QString plotAndSave(const QString& filenamepart, int plotIndex, int NUM_GRAPHS, int NUM_DATAPOINTS, double* runtimeNanoseconds=nullptr) {
|
||||||
|
QElapsedTimer timer;
|
||||||
|
timer.start();
|
||||||
|
const QString filename=QDir(QDir::tempPath()).absoluteFilePath(QString("testimg_%1_%2.png").arg(filenamepart).arg(plotIndex));
|
||||||
|
JKQTBasePlotter plot(true);
|
||||||
|
|
||||||
|
const size_t colX=plot.getDatastore()->addLinearColumn(NUM_DATAPOINTS, 0, 10, "x");
|
||||||
|
QRandomGenerator rng;
|
||||||
|
for (int i=0; i<NUM_GRAPHS; i++) {
|
||||||
|
JKQTPXYLineGraph* g;
|
||||||
|
plot.addGraph(g=new JKQTPXYLineGraph(&plot));
|
||||||
|
g->setXColumn(colX);
|
||||||
|
g->setYColumn(plot.getDatastore()->addColumnCalculatedFromColumn(colX, [&](double x) { return cos(x+double(i)/8.0*JKQTPSTATISTICS_PI)+rng.generateDouble()*0.2-0.1;}));
|
||||||
|
g->setTitle(QString("Plot %1: $f(x)=\\cos\\leftx+\\frac{%1\\pi}{8}\\right)").arg(i+1));
|
||||||
|
g->setDrawLine(true);
|
||||||
|
g->setSymbolType(JKQTPNoSymbol);
|
||||||
|
|
||||||
|
}
|
||||||
|
plot.setPlotLabel(QString("Test Plot %1").arg(plotIndex+1));
|
||||||
|
plot.getXAxis()->setAxisLabel("x-axis");
|
||||||
|
plot.getYAxis()->setAxisLabel("y-axis");
|
||||||
|
plot.zoomToFit();
|
||||||
|
plot.saveAsPixelImage(filename, false, "PNG");
|
||||||
|
|
||||||
|
if (runtimeNanoseconds) *runtimeNanoseconds=timer.nsecsElapsed();
|
||||||
|
return filename;
|
||||||
|
}
|
||||||
|
inline double getRuntimeNanosends() const {
|
||||||
|
return m_runtimeNanoseconds;
|
||||||
|
}
|
||||||
|
inline QString getFilename() const {
|
||||||
|
return m_filename;
|
||||||
|
}
|
||||||
|
protected:
|
||||||
|
inline virtual void run() {
|
||||||
|
m_filename=plotAndSave(m_filenamepart, m_plotindex, m_NUM_GRAPHS, m_NUM_DATAPOINTS, &m_runtimeNanoseconds);
|
||||||
|
}
|
||||||
|
|
||||||
|
int m_plotindex;
|
||||||
|
double m_runtimeNanoseconds;
|
||||||
|
QString m_filename;
|
||||||
|
QString m_filenamepart;
|
||||||
|
const int m_NUM_GRAPHS;
|
||||||
|
const int m_NUM_DATAPOINTS;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // MULTITHREADED_THREAD_H
|
BIN
screenshots/multithreaded.png
Normal file
BIN
screenshots/multithreaded.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 150 KiB |
BIN
screenshots/multithreaded_small.png
Normal file
BIN
screenshots/multithreaded_small.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 37 KiB |
Loading…
Reference in New Issue
Block a user