// Copyright (C) 2016 Olivier Goffart // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include #include #include #if __has_include() #include #endif #include // Wrapers that take pointers instead of reference to have the same interface as Qt template struct LockerWrapper : T { LockerWrapper(typename T::mutex_type *mtx) : T(*mtx) { } }; struct QRecursiveReadWriteLock : QReadWriteLock { QRecursiveReadWriteLock() : QReadWriteLock(Recursive) {} }; template // requires N = 2^M for some Integral M >= 0 struct Recursive { Recursive r1, r2; template Q_ALWAYS_INLINE explicit Recursive(Args &&...args) : r1(args...), r2(args...) {} }; template struct Recursive { T t; template Q_ALWAYS_INLINE explicit Recursive(Args &&...args) : t(args...) {} }; template struct Recursive { template Q_ALWAYS_INLINE explicit Recursive(Args &&...) {} }; template using QRecursiveReadLocker = Recursive; template using QRecursiveWriteLocker = Recursive; int threadCount; class tst_QReadWriteLock : public QObject { Q_OBJECT public: tst_QReadWriteLock() { // at least 2 threads, even on single cpu/core machines threadCount = qMax(2, QThread::idealThreadCount()); qDebug("thread count: %d", threadCount); } private slots: void uncontended_data(); void uncontended(); void readOnly_data(); void readOnly(); void writeOnly_data(); void writeOnly(); // void readWrite(); }; struct FunctionPtrHolder { FunctionPtrHolder(QFunctionPointer value = nullptr) : value(value) { } QFunctionPointer value; }; Q_DECLARE_METATYPE(FunctionPtrHolder) struct FakeLock { FakeLock(volatile int *i) { *i = 0; } }; enum { Iterations = 1000000 }; template void testUncontended() { Mutex lock; QBENCHMARK { for (int i = 0; i < Iterations; ++i) { Locker locker(&lock); } } } void tst_QReadWriteLock::uncontended_data() { QTest::addColumn("holder"); QTest::newRow("nothing") << FunctionPtrHolder(testUncontended); QTest::newRow("QMutex") << FunctionPtrHolder(testUncontended>); QTest::newRow("QReadWriteLock, read") << FunctionPtrHolder(testUncontended); QTest::newRow("QReadWriteLock, write") << FunctionPtrHolder(testUncontended); #define ROW(n) \ QTest::addRow("QReadWriteLock, %s, recursive: %d", "read", n) \ << FunctionPtrHolder(testUncontended>); \ QTest::addRow("QReadWriteLock, %s, recursive: %d", "write", n) \ << FunctionPtrHolder(testUncontended>) ROW(1); ROW(2); ROW(32); #undef ROW QTest::newRow("std::mutex") << FunctionPtrHolder( testUncontended>>); #ifdef __cpp_lib_shared_mutex QTest::newRow("std::shared_mutex, read") << FunctionPtrHolder( testUncontended>>); QTest::newRow("std::shared_mutex, write") << FunctionPtrHolder( testUncontended>>); #endif #if defined __cpp_lib_shared_timed_mutex QTest::newRow("std::shared_timed_mutex, read") << FunctionPtrHolder( testUncontended>>); QTest::newRow("std::shared_timed_mutex, write") << FunctionPtrHolder( testUncontended>>); #endif } void tst_QReadWriteLock::uncontended() { QFETCH(FunctionPtrHolder, holder); holder.value(); } static QHash global_hash; template void testReadOnly() { struct Thread : QThread { Mutex *lock; void run() override { for (int i = 0; i < Iterations; ++i) { QString s = QString::number(i); // Do something outside the lock Locker locker(lock); global_hash.contains(s); } } }; Mutex lock; std::vector> threads; for (int i = 0; i < threadCount; ++i) { auto t = std::make_unique(); t->lock = &lock; threads.push_back(std::move(t)); } QBENCHMARK { for (auto &t : threads) { t->start(); } for (auto &t : threads) { t->wait(); } } } void tst_QReadWriteLock::readOnly_data() { QTest::addColumn("holder"); QTest::newRow("nothing") << FunctionPtrHolder(testReadOnly); QTest::newRow("QMutex") << FunctionPtrHolder(testReadOnly>); QTest::newRow("QReadWriteLock") << FunctionPtrHolder(testReadOnly); #define ROW(n) \ QTest::addRow("QReadWriteLock, recursive: %d", n) \ << FunctionPtrHolder(testReadOnly>) ROW(1); ROW(2); ROW(32); #undef ROW QTest::newRow("std::mutex") << FunctionPtrHolder( testReadOnly>>); #ifdef __cpp_lib_shared_mutex QTest::newRow("std::shared_mutex") << FunctionPtrHolder( testReadOnly>>); #endif #if defined __cpp_lib_shared_timed_mutex QTest::newRow("std::shared_timed_mutex") << FunctionPtrHolder( testReadOnly>>); #endif } void tst_QReadWriteLock::readOnly() { QFETCH(FunctionPtrHolder, holder); holder.value(); } static QString global_string; template void testWriteOnly() { struct Thread : QThread { Mutex *lock; void run() override { for (int i = 0; i < Iterations; ++i) { QString s = QString::number(i); // Do something outside the lock Locker locker(lock); global_string = s; } } }; Mutex lock; std::vector> threads; for (int i = 0; i < threadCount; ++i) { auto t = std::make_unique(); t->lock = &lock; threads.push_back(std::move(t)); } QBENCHMARK { for (auto &t : threads) { t->start(); } for (auto &t : threads) { t->wait(); } } } void tst_QReadWriteLock::writeOnly_data() { QTest::addColumn("holder"); // QTest::newRow("nothing") << FunctionPtrHolder(testWriteOnly); QTest::newRow("QMutex") << FunctionPtrHolder(testWriteOnly>); QTest::newRow("QReadWriteLock") << FunctionPtrHolder(testWriteOnly); #define ROW(n) \ QTest::addRow("QReadWriteLock, recursive: %d", n) \ << FunctionPtrHolder(testWriteOnly>) ROW(1); ROW(2); ROW(32); #undef ROW QTest::newRow("std::mutex") << FunctionPtrHolder( testWriteOnly>>); #ifdef __cpp_lib_shared_mutex QTest::newRow("std::shared_mutex") << FunctionPtrHolder( testWriteOnly>>); #endif #if defined __cpp_lib_shared_timed_mutex QTest::newRow("std::shared_timed_mutex") << FunctionPtrHolder( testWriteOnly>>); #endif } void tst_QReadWriteLock::writeOnly() { QFETCH(FunctionPtrHolder, holder); holder.value(); } QTEST_MAIN(tst_QReadWriteLock) #include "tst_bench_qreadwritelock.moc"