Интересная проблема. Тестирование на Windows, Qt 5.12.4.
Одна вещь, которую я до сих пор определял, заключается в том, что std::vector
, кажется, работает лучше в этом случае. Но это все еще довольно долго и влияет на другие потоки в системе, делая пользовательский интерфейс лишь несколько отзывчивым. Но лучше, чем QVector
.
Кроме того, это большие числа и требуют значительного объема памяти. На моей 32-битной сборке MinGw происходит сбой из-за нехватки памяти, когда я пытаюсь использовать> 2 потока. Поэтому испытания были проведены с 64b MSVC2017. Тестовая машина имеет 8 ядер @ 3. ГГц с 64 ГБ ОЗУ.
Вот некоторые временные результаты (код, используемый для генерации этого, приведен ниже):
1 worker with 2 `std::vector`s:
Worker 1 finished (ms) 1648
Last worker finished after 1649 total ms.
5 workers with 2 `std::vector`s:
Worker 1 finished (ms) 44363
Worker 2 finished (ms) 44386
Worker 3 finished (ms) 44388
Worker 4 finished (ms) 44401
Worker 5 finished (ms) 44448
Last worker finished after 44449 total ms.
10 workers with 2 `std::vector`s:
Worker 4 finished (ms) 84910
Worker 7 finished (ms) 92701
Worker 2 finished (ms) 111590
Worker 8 finished (ms) 144678
Worker 9 finished (ms) 145378
Worker 5 finished (ms) 169067
Worker 3 finished (ms) 211629
Worker 1 finished (ms) 220098
Worker 10 finished (ms) 249356
Worker 6 finished (ms) 253452
Last worker finished after 253453 total ms.
1 worker with 2 `QVector`s:
Worker 1 finished (ms) 1871
Last worker finished after 1872 total ms.
5 workers with 2 `QVector`s:
Worker 1 finished (ms) 36492
Worker 3 finished (ms) 58157
Worker 5 finished (ms) 79132
Worker 2 finished (ms) 84612
Worker 4 finished (ms) 84819
Last worker finished after 84820 total ms.
10 workers with 2 `QVector`s:
Worker 7 finished (ms) 234770
Worker 8 finished (ms) 247531
Worker 9 finished (ms) 261346
Worker 1 finished (ms) 261924
Worker 4 finished (ms) 270520
Worker 2 finished (ms) 275740
Worker 10 finished (ms) 290605
Worker 3 finished (ms) 293575
Worker 6 finished (ms) 296074
Worker 5 finished (ms) 296249
Last worker finished after 296361 total ms.
В определенный момент между 5 и 10 потоками даже std::vector
кажетсяначать «спотыкаться». Это также очевидно в отзывчивости GUI (в некоторой степени реагирует на 5, почти не на 10).
Как уже упоминалось в комментариях к OP, задержка происходит во время отмены выделения больших векторов traces1
иtraces2
, по-видимому, не во время clear()
(или swap()
в этом отношении). Но единственный способ определить это с помощью отладчика, потому что, как только он достигает конца функции clearVector()
, поток, по сути, зависает (попытка установить метку времени с помощью таймера бесполезна).
Я также пыталсяиспользуя только 1 вектор «set» внутри Worker
(см. код). Огромная разница:
10 workers with 1 `std::vector`:
Worker 5 finished (ms) 4125
Worker 4 finished (ms) 4139
Worker 1 finished (ms) 4141
Worker 6 finished (ms) 4153
Worker 10 finished (ms) 4161
Worker 9 finished (ms) 4177
Worker 7 finished (ms) 4197
Worker 3 finished (ms) 4216
Worker 8 finished (ms) 4209
Worker 2 finished (ms) 4221
Last worker finished after 4222 total ms.
10 workers with 1 `QVector`:
Worker 10 finished (ms) 4308
Worker 2 finished (ms) 4358
Worker 1 finished (ms) 4373
Worker 3 finished (ms) 4385
Worker 8 finished (ms) 4391
Worker 4 finished (ms) 4400
Worker 6 finished (ms) 4404
Worker 7 finished (ms) 4401
Worker 5 finished (ms) 4409
Worker 9 finished (ms) 4406
Last worker finished after 4410 total ms.
Вот мой тестовый "стенд":
#include <QRunnable>
#include <QThread>
#include <QElapsedTimer>
#include <QtWidgets>
#define USE_QVECTOR 0
#define NUM_VECTORS 2
#define USE_CLEAR 0
#define USE_SWAP 0
class Worker : public QObject, public QRunnable
{
Q_OBJECT
public:
#if USE_QVECTOR
typedef QVector<int> vect_t;
typedef QVector<vect_t> vectVect_t;
#else
typedef std::vector<int> vect_t;
typedef std::vector<vect_t> vectVect_t;
#endif
explicit Worker(int id, int traces, int samples, QObject *parent = nullptr) :
QObject(parent), QRunnable(),
id(id), numTraces(traces), numSamps(samples)
{}
void run() override
{
qDebug() << "worker starting" << id << numTraces << numSamps << QThread::currentThread();
emit progressChanged(id, -1);
tim.start();
clearVector();
emit progressChanged(id, tim.elapsed());
}
signals:
void progressChanged(int id, int pos) const;
private:
void clearVector()
{
vectVect_t traces1, traces2;
traces1.reserve(numTraces);
if (NUM_VECTORS > 1)
traces2.reserve(numTraces);
float progressWaypoint = 0.01f * numTraces;
int progressPos = 0;
for (int i=0; i < numTraces; i++) {
vect_t trace1, trace2;
trace1.reserve(numSamps);
if (NUM_VECTORS > 1)
trace2.reserve(numSamps);
for (int j=0; j < numSamps; j++) {
trace1.push_back(j);
if (NUM_VECTORS > 1)
trace2.push_back(numSamps - j);
}
traces1.push_back(trace1);
if (NUM_VECTORS > 1)
traces2.push_back(trace2);
if (i + 1 >= round(progressWaypoint * progressPos))
emit progressChanged(id, progressPos++);
}
qDebug() << "Vectors populated in" << tim.elapsed();
if (USE_CLEAR) {
// Clearing the vectors slows the process down a bit but its not where the delay is.
traces1.clear();
if (NUM_VECTORS > 1)
traces2.clear();
}
if (USE_SWAP) {
// swap is very fast but it doesn't help overall performance
vectVect_t blank;
traces1.swap(blank);
if (NUM_VECTORS > 1)
traces2.swap(blank);
}
}
int id, numTraces, numSamps;
QElapsedTimer tim;
};
int main(int argc, char *argv[]) {
QApplication a(argc, argv);
// UI setup
QDialog d;
d.setLayout(new QVBoxLayout());
QPushButton *pbStart = new QPushButton("Start", &d);
QSpinBox *sbThreads = new QSpinBox(&d);
sbThreads->setValue(5);
QSpinBox *sbTraces = new QSpinBox(&d);
sbTraces->setMaximum(10000);
sbTraces->setValue(10000);
QSpinBox *sbSamps = new QSpinBox(&d);
sbSamps->setMaximum(10000);
sbSamps->setValue(8680);
QHBoxLayout *btnLo = new QHBoxLayout();
btnLo->setSpacing(6);
btnLo->addWidget(pbStart);
btnLo->addWidget(new QLabel("Thrds:", &d));
btnLo->addWidget(sbThreads, 1);
btnLo->addWidget(new QLabel("Traces:", &d));
btnLo->addWidget(sbTraces, 1);
btnLo->addWidget(new QLabel("Samps:", &d));
btnLo->addWidget(sbSamps, 1);
d.layout()->addItem(btnLo);
// Text box for showing results
QTextEdit *e = new QTextEdit(&d);
e->setReadOnly(true);
e->setTextInteractionFlags(Qt::TextBrowserInteraction);
d.layout()->addWidget(e);
QElapsedTimer tim; // total elapsed timer
QVector<int> finished; // keep track of finished workers
// Set up workers on button click.
QObject::connect(pbStart, &QPushButton::clicked, &d, [&]()
{
const int threads = sbThreads->value(),
traces = sbTraces->value(),
samples = sbSamps->value();
QThreadPool *pool = QThreadPool::globalInstance();
//pool->setStackSize(samples * 4 * traces * threads);
qDebug() << "Pool max. threads:" << pool->maxThreadCount() << "Stack size:" << pool->stackSize();
pbStart->setDisabled(true);
finished.clear();
tim.start();
for (int i=0; i < threads; i++) {
Worker *w = new Worker(i+1, traces, samples);
// Show messages on worker progress updates
QObject::connect(w, &Worker::progressChanged, &d, [e, pbStart, threads, &tim, &finished](int id, int pos)
{
const QString msg = QStringLiteral("Worker %1 %2 %3")
.arg(id)
.arg(pos < 0 ? "started" : pos > 100 ? "finished (ms)" : "progress")
.arg(pos);
e->append(msg);
if (pos > 100) {
finished << id;
if (finished.count() == threads) {
e->append(QStringLiteral("Last worker finished after %1 total ms.").arg(tim.elapsed()));
pbStart->setEnabled(true);
}
}
e->ensureCursorVisible();
}, Qt::QueuedConnection);
w->setAutoDelete(true);
pool->start(w);
qDebug() << "Queued worker" << i+1 << "with active thread count:" << pool->activeThreadCount();
}
});
d.show();
return a.exec();
}
#include "main.moc"
ДОБАВЛЕНО: Использование массивов фиксированного размера вместо векторов. Очевидно, что в реальном коде нужно будет позаботиться о том, чтобы индексы массива действительно действовали. (Также, конечно, можно заполнить массивы traces1
и traces2
непосредственно во внутреннем цикле, без промежуточного trace1/2
, но пока что NVM.:)
void clearVector()
{
float progressWaypoint = 0.01f * numTraces;
int progressPos = 0;
// volatile to help make sure the compiler isn't just optimizing these out.
volatile int *traces1[10000], *traces2[10000];
for (int i=0; i < numTraces; i++) {
volatile int trace1[10000], trace2[10000];
for (int j=0; j < numSamps; j++) {
trace1[j] = j;
trace2[j] = (numSamps - j);
}
traces1[i] = trace1;
traces2[i] = trace2;
if (i + 1 >= round(progressWaypoint * progressPos))
emit progressChanged(id, progressPos++);
}
// also use a value from the populated arrays to make sure they really exist.
qDebug() << "Vectors populated in" << tim.elapsed() << traces1[0][0] << traces2[5][5];
}
У меня былодобавить 100
к номеру таймера, потому что каждый поток заканчивается <100 мс. </p>
void run() override {
...
clearVector();
emit progressChanged(id, tim.elapsed() + 100);
}
С 20 потоками (16 непосредственных и 4 попадают в очередь) и 10K каждого из "следов" и "образцов", которые я получаю:
Последний рабочий закончил после 332 мсек.
Также это не вызывает проблем в моей 32-битной сборке MinGW с 20 потоками. То же самое время выполнения.