1

In my program I am reading a lot of data from a text file & putting it to a QTableWidget. Initially it was all done in the GUI thread & then I decided to multi thread it to have better performance. But on the contrary the performance was significantly slow i.e. 8x slower! So I decided to benchmark it. Here are my files:

main.cpp

#include <QtGui/QApplication>
#include <QMutex>
#include <QDir>
#include <string>
#include <QDebug>
#include "mainwindow.h"

QFile *logFile = NULL;
QTextStream *logStream = NULL;
QMutex *mutex = NULL;
bool *debugMode = NULL;

void myMessageOutput(QtMsgType type, const char *msg)
 {
    if(((logFile != NULL) && (debugMode != NULL)))
    {
        mutex->lock();
         switch (type)
         {
         case QtDebugMsg:
             if(!*debugMode)
             {
                 mutex->unlock();
                 return;
             }
             *logStream << msg;
             logStream->flush();
             break;
         case QtWarningMsg:
             *logStream << "\n*** Warning ***\n";
             *logStream << msg;
             *logStream << "\n*** Warning Complete ***\n";
             logStream->flush();
             break;
         case QtCriticalMsg:
             *logStream << "\n*** Critical ***\n";
             *logStream << msg;
             *logStream << "\n*** Critical Complete ***\n";
             logStream->flush();
             break;
         case QtFatalMsg:
             *logStream << "\n*** Fatal ***\n";
             *logStream << msg;
             *logStream << "\n*** Fatal Complete ***\n";
             logStream->flush();
             abort();
         }
         mutex->unlock();
    }
 }

void CreateLogFile()
{
    QString path = "C:\\Users\\abcd\\Documents\\QT\\benchmark\\output.log";
    QFile *file = new QFile(path);
    if(file->exists())
        file->remove();
    if(!file->open(QFile::WriteOnly | QFile::Text))
    {
        qFatal("Could not create log file.");
    }

    logStream = new QTextStream(file);
    logStream->setRealNumberNotation(QTextStream::FixedNotation);
    logStream->setRealNumberPrecision(16);
    logFile = file;
}

int main(int argc, char *argv[])
{
    mutex = new QMutex();
    qInstallMsgHandler(myMessageOutput);
    debugMode = new bool;
    CreateLogFile();
    *debugMode = true;

    QApplication a(argc, argv);
    MainWindow w;
    w.show();
    w.bench2();

    return a.exec();

}

mainwindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QThread>
#include "multi_thread.h"

namespace Ui {
class MainWindow;
}

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    explicit MainWindow(QWidget *parent = 0);
    ~MainWindow();
    void bench2();

private:
    Ui::MainWindow *ui;
};

#endif // MAINWINDOW_H

mainwindow.cpp

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QDebug>
#include <QList>
#include <QTime>

MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow)
{
    ui->setupUi(this);
}

MainWindow::~MainWindow()
{
    delete ui;
}

void MainWindow::bench2()
{
    QTableWidget *table = new QTableWidget();
    table->setRowCount(1000);
    table->setColumnCount(1000);
    for(int i = 0; i < 1000; i++)
    {
        for(int j = 0; j < 1000; j++)
            table->setItem(i, j, new QTableWidgetItem());
    }
    Multi_Thread **multis = new Multi_Thread *[4];
    QThread **thrs = new QThread *[4];
    int from;
    int to = -1;

    QTime time;
    time.start();

    for(int i = 0; i < 4; i++)
    {
        from = to + 1;
        to = from + 250;
        if(i == 3)
            to = 999;

        multis[i] = new Multi_Thread();
        multis[i]->setTable(table, from, to);
        thrs[i] = new QThread();
        connect(thrs[i], SIGNAL(started()), multis[i], SLOT(bench2_1()));
        multis[i]->moveToThread(thrs[i]);
    }

    for(int i = 0; i < 4; i++)
    {
        if(!i)
            time.start();

        thrs[i]->start();
    }

    for(int i = 0; i < 4; i++)
    {
        thrs[i]->wait();
    }
    qDebug() << "\nbench2 1 " << time.elapsed();

    for(int i = 0; i < 4; i++)
    {
        delete multis[i];
        delete thrs[i];
    }
    delete[] multis;
    delete[] thrs;


    table->clear();
    table->setRowCount(1000);
    table->setColumnCount(1000);
    for(int i = 0; i < 1000; i++)
    {
        for(int j = 0; j < 1000; j++)
            table->setItem(i, j, new QTableWidgetItem());
    }

    time.start();

    for(int i = 0; i < 1000; i++)
    {
        for(int j = 0; j < 1000; j++)
            table->item(i, j)->setText("0");
    }
    qDebug() << "\nbench2 2 " << time.elapsed();

    table->clear();
    table->setRowCount(1000);
    table->setColumnCount(1000);
    QTableWidgetItem ***items = new QTableWidgetItem **[1000];
    for(int i = 0; i < 1000; i++)
    {
        items[i] = new QTableWidgetItem *[1000];
        for(int j = 0; j < 1000; j++)
        {
            QTableWidgetItem *item = new QTableWidgetItem();
            table->setItem(i, j, item);
            items[i][j] = item;
        }
    }

    multis = new Multi_Thread *[4];
    thrs = new QThread *[4];
    to = -1;

    for(int i = 0; i < 4; i++)
    {
        from = to + 1;
        to = from + 250;
        if(i == 3)
            to = 999;

        multis[i] = new Multi_Thread();
        multis[i]->setItems(items, from, to);
        thrs[i] = new QThread();
        connect(thrs[i], SIGNAL(started()), multis[i], SLOT(bench2_2()));
        multis[i]->moveToThread(thrs[i]);
    }
    table->blockSignals(true);
    table->setUpdatesEnabled(false);
    table->setWordWrap(false);

    for(int i = 0; i < 4; i++)
    {
        if(!i)
            time.start();

        thrs[i]->start();
    }

    for(int i = 0; i < 4; i++)
    {
        thrs[i]->wait();
    }
    qDebug() << "\nbench2 3 " << time.elapsed();
    table->blockSignals(false);
    table->setUpdatesEnabled(true);
    table->setWordWrap(true);

    for(int i = 0; i < 4; i++)
    {
        delete multis[i];
        delete thrs[i];
    }
    delete[] multis;
    delete[] thrs;

    table->clear();
    for(int i = 0; i < 1000; i++)
    {
        delete[] items[i];
    }
    delete[] items;

}

multi_thread.h

#ifndef MULTI_THREAD_H
#define MULTI_THREAD_H

#include <QObject>
#include <QThread>
#include <QTableWidget>

class Multi_Thread : public QObject
{
    Q_OBJECT
public:
    explicit Multi_Thread();
    void setTable(QTableWidget *tab, int f, int t);
    void setItems(QTableWidgetItem ***i, int f, int t);

private:
    QTableWidget *table;
    QTableWidgetItem ***items;
    int from;
    int to;

signals:

public slots:
    void bench2_1();
    void bench2_2();

};

#endif // MULTI_THREAD_H

multi_thread.cpp

#include "multi_thread.h"

Multi_Thread::Multi_Thread() : QObject()
{
}

void Multi_Thread::setTable(QTableWidget *tab, int f, int t)
{
    table = tab;
    from = f;
    to = t;
}

void Multi_Thread::setItems(QTableWidgetItem ***i, int f, int t)
{
    items = i;
    from = f;
    to = t;
}

void Multi_Thread::bench2_1()
{
    for(int i = from; i <= to; i++)
    {
        for(int j = 0; j < 1000; j++)
        {
            table->item(i, j)->setText("0");
        }
    }
    QThread::currentThread()->exit(0);
}

void Multi_Thread::bench2_2()
{
    for(int i = from; i <= to; i++)
    {
        for(int j = 0; j < 1000; j++)
        {
            items[i][j]->setText("0");
        }
    }
    QThread::currentThread()->exit(0);
}

output.log

bench2 1  7654 
bench2 2  1160 
bench2 3  8021 

What is strange is that I was expecting "bench2 3" to be faster than "bench2 1".

PS: My laptop hardware can needs 4 threads to reach 100% usage. Please edit it as per your hardware requirements. Can be known from Environment variables.

Cool_Coder
  • 4,888
  • 16
  • 57
  • 99

2 Answers2

2

This answer has a different strategy for getting large amounts of data into a QTableView very efficiently (time and memory). I also cached a parsed row of data for successive requests to the same row.

Community
  • 1
  • 1
Jason
  • 389
  • 2
  • 6
  • Hi Jason, I saw your answer to that question. I really liked the concept of not putting the data in memory rather reading as required. But I could not understand the details. For example, I am unaware of what 'cache' or 'parse' is. – Cool_Coder Sep 18 '13 at 15:43
  • In this context, 'parse' means to take a delimited string and break it up into a string list. For example `"one,two,three,four"` would split into `["one", "two", "three", "four"]`. And 'cache' means the model class has a member variable to hold onto some information about the file. For example, it caches the list of file offsets for each line and caches the list of strings for the 'current' row. – Jason Sep 18 '13 at 19:57
2

Calling table->item(...)->setText() from any thread other than the GUI thread is undefined behavior. Don't do it. It's nice that you're using a QObject in a QThread to do the job, but you must not directly call methods on objects that live in other threads.

The internal model will call the view's dataChanged() slot for each of your updates. This is the likely source of the slow-down, apart from the memory allocations for each item.

The typical way of preparing a data model in a separate thread would be to:

  1. Instantiate the model in a separate thread. The model must not yet be connected to any views at this point. Initialize the model with data. It can be a QStandardItemModel or a custom model. Custom models are not that hard, and can often be much more efficient if you don't need to perform a memory allocation per each item.

  2. Connect the model to the view (QTableView, not QTableWidget). You can move it to the gui thread for convenience, although you don't have to.

  3. Only access the model from the thread it lives in, unless you use queued method invocations or queued signal-slot connections. The view uses the latter and thus can live in a thread separate from the model.

A QTableWidget bundles an internal QStandardItemModel with a QTableView. If you want to deal with the model separately, you simply use the the model class in isolation from the view class.

Kuba hasn't forgotten Monica
  • 95,931
  • 16
  • 151
  • 313
  • Hi Kuba, I am beginner in Qt or rather programming as such. Can you please show some code for what you have said. I am still trying to understand the model/view framework. I am guessing that you are suggesting me to use my own widget instead of QTableWidget. Can you please show this by code also? – Cool_Coder Sep 18 '13 at 15:47
  • Don't use your own widget. Use a `QTableView` and an explicit model like `QStandardItemModel`. – Kuba hasn't forgotten Monica Sep 18 '13 at 15:52
  • yes that is what I meant. So can you please show how to use it by code? – Cool_Coder Sep 18 '13 at 15:54
  • 1
    If you wrote the code in the question yourself, you should have no problem reading the documentation and figuring it out. Follow the enumarated list. Step 1 is to create the model and fill it with data. The documentation should be sufficient, but if you have any specific questions, feel free to ask. I may create a short example later, but I implore you to attempt to do the work yourself first. I gave you the class names, that's all you need to get the documentation :) – Kuba hasn't forgotten Monica Sep 18 '13 at 15:55