File indexing completed on 2024-12-22 04:10:30

0001 /*
0002  *  SPDX-FileCopyrightText: 2010 Dmitry Kazakov <dimula73@gmail.com>
0003  *
0004  *  SPDX-License-Identifier: GPL-2.0-or-later
0005  */
0006 
0007 #include "kis_lockless_stack_test.h"
0008 #include <simpletest.h>
0009 
0010 #include "kis_debug.h"
0011 
0012 #include "kis_lockless_stack.h"
0013 #include "config-limit-long-tests.h"
0014 
0015 void KisLocklessStackTest::testOperations()
0016 {
0017     KisLocklessStack<int> stack;
0018 
0019 
0020     for(qint32 i = 0; i < 1024; i++) {
0021         stack.push(i);
0022     }
0023 
0024     QCOMPARE(stack.size(), 1024);
0025 
0026     for(qint32 i = 1023; i >= 0; i--) {
0027         int value;
0028 
0029         bool result = stack.pop(value);
0030         QVERIFY(result);
0031 
0032         QCOMPARE(value, i);
0033     }
0034 
0035     QVERIFY(stack.isEmpty());
0036 
0037 }
0038 
0039 /************ BENCHMARKING INFRASTRUCTURE ************************/
0040 
0041 #define NUM_TYPES 2
0042 
0043 // high-concurrency
0044 #define NUM_CYCLES 500000
0045 #define NUM_THREADS 10
0046 
0047 // relaxed
0048 //#define NUM_CYCLES 100
0049 //#define NUM_THREADS 2
0050 
0051 // single-threaded
0052 //#define NUM_CYCLES 10000000
0053 //#define NUM_THREADS 1
0054 
0055 
0056 class KisAbstractIntStack
0057 {
0058 public:
0059     virtual ~KisAbstractIntStack() {}
0060     virtual void push(int value) = 0;
0061     virtual int pop() = 0;
0062     virtual bool isEmpty() = 0;
0063     virtual void clear() = 0;
0064 };
0065 
0066 class KisTestingLocklessStack : public KisAbstractIntStack
0067 {
0068 public:
0069     void push(int value) override {
0070         m_stack.push(value);
0071     }
0072 
0073     int pop() override {
0074         int value  = 0;
0075 
0076         bool result = m_stack.pop(value);
0077         Q_ASSERT(result);
0078         Q_UNUSED(result); // for release build
0079 
0080         return value;
0081     }
0082 
0083     bool isEmpty() override {
0084         return m_stack.isEmpty();
0085     }
0086 
0087     void clear() override {
0088         m_stack.clear();
0089     }
0090 
0091 private:
0092     KisLocklessStack<int> m_stack;
0093 };
0094 
0095 class KisTestingLegacyStack : public KisAbstractIntStack
0096 {
0097 public:
0098     void push(int value) override {
0099         m_mutex.lock();
0100         m_stack.push(value);
0101         m_mutex.unlock();
0102     }
0103 
0104     int pop() override {
0105         m_mutex.lock();
0106         int result = m_stack.pop();
0107         m_mutex.unlock();
0108 
0109         return result;
0110     }
0111 
0112     bool isEmpty() override {
0113         m_mutex.lock();
0114         bool result = m_stack.isEmpty();
0115         m_mutex.unlock();
0116 
0117         return result;
0118     }
0119 
0120     void clear() override {
0121         m_mutex.lock();
0122         m_stack.clear();
0123         m_mutex.unlock();
0124     }
0125 
0126 private:
0127     QStack<int> m_stack;
0128     QMutex m_mutex;
0129 };
0130 
0131 
0132 class KisStressJob : public QRunnable
0133 {
0134 public:
0135     KisStressJob(KisAbstractIntStack &stack, qint32 startValue)
0136         : m_stack(stack), m_startValue(startValue)
0137     {
0138         m_pushSum = 0;
0139         m_popSum = 0;
0140     }
0141 
0142     void run() override {
0143         for(qint32 i = 0; i < NUM_CYCLES; i++) {
0144             qint32 type = i % NUM_TYPES;
0145             int newValue;
0146 
0147             switch(type) {
0148             case 0:
0149                 newValue = m_startValue + i;
0150 
0151                 m_pushSum += newValue;
0152                 m_stack.push(newValue);
0153                 break;
0154             case 1:
0155                 m_popSum += m_stack.pop();
0156                 break;
0157             }
0158         }
0159     }
0160 
0161     qint64 pushSum() {
0162         return m_pushSum;
0163     }
0164 
0165     qint64 popSum() {
0166         return m_popSum;
0167     }
0168 
0169 private:
0170     KisAbstractIntStack &m_stack;
0171     qint32 m_startValue;
0172     qint64 m_pushSum;
0173     qint64 m_popSum;
0174 };
0175 
0176 void KisLocklessStackTest::runStressTest(KisAbstractIntStack &stack)
0177 {
0178     QList<KisStressJob*> jobsList;
0179     KisStressJob *job;
0180 
0181     for(qint32 i = 0; i < NUM_THREADS; i++) {
0182         job = new KisStressJob(stack, 1);
0183         job->setAutoDelete(false);
0184         jobsList.append(job);
0185     }
0186 
0187     QThreadPool pool;
0188     pool.setMaxThreadCount(NUM_THREADS);
0189 
0190     QBENCHMARK {
0191         Q_FOREACH (job, jobsList) {
0192             pool.start(job);
0193         }
0194 
0195         pool.waitForDone();
0196     }
0197 
0198     QVERIFY(stack.isEmpty());
0199 
0200     qint64 totalSum = 0;
0201 
0202     for(qint32 i = 0; i < NUM_THREADS; i++) {
0203         KisStressJob *job = jobsList.takeLast();
0204 
0205         totalSum += job->pushSum();
0206         totalSum -= job->popSum();
0207 
0208         dbgKrita << ppVar(totalSum);
0209 
0210         delete job;
0211     }
0212 
0213     QCOMPARE(totalSum, (long long) 0);
0214 }
0215 
0216 void KisLocklessStackTest::stressTestLockless()
0217 {
0218     KisTestingLocklessStack stack;
0219     runStressTest(stack);
0220 }
0221 
0222 void KisLocklessStackTest::stressTestQStack()
0223 {
0224     KisTestingLegacyStack stack;
0225     runStressTest(stack);
0226 }
0227 
0228 class KisStressClearJob : public QRunnable
0229 {
0230 public:
0231     KisStressClearJob(KisLocklessStack<int> &stack, qint32 startValue)
0232         : m_stack(stack), m_startValue(startValue)
0233     {
0234     }
0235 
0236     void run() override {
0237         for(qint32 i = 0; i < NUM_CYCLES; i++) {
0238             qint32 type = i % 4;
0239             int newValue;
0240 
0241             switch(type) {
0242             case 0:
0243             case 1:
0244                 newValue = m_startValue + i;
0245                 m_stack.push(newValue);
0246                 break;
0247             case 2:
0248                 int tmp;
0249                 m_stack.pop(tmp);
0250                 break;
0251             case 3:
0252                 m_stack.clear();
0253                 break;
0254             }
0255         }
0256     }
0257 
0258 private:
0259     KisLocklessStack<int> &m_stack;
0260     qint32 m_startValue;
0261 };
0262 
0263 void KisLocklessStackTest::stressTestClear()
0264 {
0265     KisLocklessStack<int> stack;
0266     KisStressClearJob *job;
0267 
0268     QThreadPool pool;
0269     pool.setMaxThreadCount(NUM_THREADS);
0270 
0271     for(qint32 i = 0; i < NUM_THREADS; i++) {
0272         job = new KisStressClearJob(stack, 1);
0273         pool.start(job);
0274     }
0275     pool.waitForDone();
0276 
0277     stack.clear();
0278     QVERIFY(stack.isEmpty());
0279 }
0280 
0281 class KisStressBulkPopJob : public QRunnable
0282 {
0283 public:
0284     KisStressBulkPopJob(KisLocklessStack<int> &stack, qint64 &removedCheckSum)
0285         : m_stack(stack), m_removedCheckSum(removedCheckSum)
0286     {
0287     }
0288 
0289     void run() override {
0290         int value = 0;
0291         while (m_stack.pop(value)) {
0292             m_removedCheckSum += value;
0293         }
0294     }
0295 
0296 private:
0297     KisLocklessStack<int> &m_stack;
0298     qint64 &m_removedCheckSum;
0299 };
0300 
0301 void KisLocklessStackTest::stressTestBulkPop()
0302 {
0303     qsrand(10);
0304 
0305     KisLocklessStack<int> stack;
0306 
0307 #ifdef LIMIT_LONG_TESTS
0308     const int numThreads = 3;
0309     const int numObjects = 10000000;
0310 #else
0311     const int numThreads = 3;
0312     const int numObjects = 10000000;
0313 #endif
0314 
0315     QThreadPool pool;
0316     pool.setMaxThreadCount(numThreads);
0317 
0318     qint64 expectedSum = 0;
0319     for (int i = 0; i < numObjects; i++) {
0320         const int value = i % 3 + 1;
0321         expectedSum += value;
0322         stack.push(value);
0323     }
0324 
0325     QVector<qint64> partialSums(numThreads);
0326 
0327     for(qint32 i = 0; i < numThreads; i++) {
0328         KisStressBulkPopJob *job = new KisStressBulkPopJob(stack, partialSums[i]);
0329         pool.start(job);
0330     }
0331     pool.waitForDone();
0332 
0333     QVERIFY(stack.isEmpty());
0334 
0335     const qint64 realSum = std::accumulate(partialSums.begin(), partialSums.end(), 0);
0336     QCOMPARE(realSum, expectedSum);
0337 }
0338 
0339 SIMPLE_TEST_MAIN(KisLocklessStackTest)
0340