13.10.2017

foto Petr Bravenec

Petr Bravenec
Twitter: @BravenecPetr
+420 777 566 384
petr.bravenec@hobrasoft.cz

In the last article of parallel programming in Qt, I described the use of non-blocking operations and using of the QFuture class. Non-blocking operations are very important in GUI application. Non-blocking operations allows you to control the calculation or work in other parts of the application during the calculations.

Since the last part, a lot of time has run away. Meanwhile, I started to develop »Artificial Intelligence Photovoltaic Analyser«. Photovoltaic power plants produce a lot of data and the parallel data processing is necessary, so I have the techniques described in this article well tested.

QFutureWatcher class

In the last article I slightly outlined the use of the QFuture class for non-blocking operations. Due to using the QFuture::waitForFinished() method the final application was blocking, but the principle could by understood from the example. If you want to use non-blocking operations, you must ensure that the input dataset and the QFuture and QFutureWatcher instance exist during the lifetime of the calculations.

The QFutureWatcher class has slots for calculation control and signals to report calculations progress and status. It is needed to connect these signals and slots with GUI control widgets.

class Calculation : public QWidget, private Ui::Calculation {
    Q_OBJECT
  public:

    // GUI contains buttons "Start", "Pause" and "Cancel"
    // The "Pause" button is used to pause and to resume calculation.
    // The progress is shown using QProgressBar
    Calculation(QWidget *parent);

  public slots:
    void    run();

  private slots:
    void    slotFinished();
    void    slotPauseChanged();

  private:
    QFuture<void>          m_future;
    QFutureWatcher<void>   m_watcher;
    QList<int>             m_inputvalues; // Vstupní hodnoty pro výpočet
    void                   setEnabled(bool);
    static void            coreFunction(int);
};


// Contructor connects buttons with parallel computation (the m_watcher member).
// Signals from QFutureWatcher (m_watcher) are connected to GUI
Calculation(QWidget *parent) : QWidget(parent) {
    setupUi(this);
    // Connect buttons to m_watcher
    connect(f_tlacitko_run, SIGNAL(clicked()), this, SLOT(run()));
    connect(f_tlacitko_cancel, SIGNAL(clicked()), &m_watcher, SLOT(cancel()));
    connect(f_tlacitko_pause, SIGNAL(clicked()), &m_watcher, SLOT(togglePause()));
    connect(&m_watcher, SIGNAL(progressValueChanged(int)),
            f_progressbar, SLOT(valueChanged(int)));
    connect(&m_watcher, SIGNAL(finished()),
            this, SLOT(slotFinished()));
    connect(&m_watcher, SIGNAL(paused()), this, SLOT(slotPauseChanged()));
    connect(&m_watcher, SIGNAL(resumed()), this, SLOT(slotPauseChanged()));
}

// Changes text to "Pause" or "Resume" depending of calculation state
void Calculation::slotPauseChanged() {
    f_tlacitko_pause->setText( (m_watcher.isPaused()) ? tr("Resume") : tr("Pause") );
}

// Cleans after calculation
void Calculation::slotFinished() {
    m_inputvalues.clear();
    setEnabled(true);
}

// Enables or disables buttons
void Calculation::setEnabled(bool enable) {
    f_tlacitko_run(enable);
    f_tlacitko_cancel(!enable);
    f_tlacitko_pause(!enable);
    f_progressbar(!enable);
}

// Starts the calculation 
// Input data are read from database (object DB)
void run() {
    setEnabled(false);
    f_tlacitko_pause->setText( tr("Pause") );
    m_inputvalues = DB->nactiVstupniHodnoty();
    f_progressbar->setMaximum(m_inputvalues.size());
    m_future = QtConcurrent::map(m_inputvalues, coreFunction);
    m_watcher.setFuture(m_future);
}

// Parallel computation
// Input values are stored in m_inputvalues
// The function is static.
// The function must be reentrant and multi thread safe.
// If you connect to database, it is needed to have independend 
// database connection in every thread.
// You cannot access to GUI but you can emit signals to GUI.
void coreFunction(int x) {
    Database *db = new Database();
    db->readData(); 
    // calculations here
    db->saveData();
    delete db;
}

Experience

If you stop you parallel calculations, it can take some time to stop the calculation. When you stop the calculation using the "Cancel" or "Pause" button, no more new calculations are planned. The calculation is finaly stopped when the last calculation core finishes its job. For example, if one calculation last one minute, the calculation is not finished before that time.

Using the QThreadStorage class may be problematic. Although the same pool of threads is used for the calculation, threads are created before parallel calculation. I tried to use the class for persistent database connection, but finally I had to use independend database connection in every core run.

It is best to use independend database connection. For this reason you should avoid SQLite database. The SQLite cannot be used in multi-thread applications.

If the database engine and the parallel computation runs in one server, the acceleration is less then eight-times on eight-core processor. If you can, store your database in independend server. Then the acceleration could be more significant.

Links

Conclusion

Processing large ammounts of data is part of my work. Parallel processing in Qt is simple to use and it accelerates the calculation significantly on multi-core processors. Single-thread calculation can last whole week (starts Monday, Friday result are available). The same calculation can be finished overnight using my eight-core AMD FX8350 processor. The CPU can be bought today at a price of around 3600 CZK.

By the way – I'm looking forward to ThreadRipper processor. With the 16-core CPU I can expect more then five times faster calculations compared to my present processor.

Hobrasoft s.r.o. | Contact