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