diff --git a/libs/image/tiles3/kis_tile_hash_table2.h b/libs/image/tiles3/kis_tile_hash_table2.h index 3325c6d97f..6c76f2a7ab 100644 --- a/libs/image/tiles3/kis_tile_hash_table2.h +++ b/libs/image/tiles3/kis_tile_hash_table2.h @@ -1,95 +1,113 @@ #ifndef KIS_TILEHASHTABLE_2_H #define KIS_TILEHASHTABLE_2_H #include "kis_shared.h" #include "kis_shared_ptr.h" #include "3rdparty/lock_free_map/concurrent_map.h" template class KisTileHashTableTraits2 { - static_assert(std::is_convertible::value, "Template must inherit KisShared"); + static constexpr bool isInherited = std::is_convertible::value; + Q_STATIC_ASSERT_X(isInherited, "Template must inherit KisShared"); public: typedef T TileType; typedef KisSharedPtr TileTypeSP; typedef KisWeakSharedPtr TileTypeWSP; - typedef typename ConcurrentMap::Iterator Iterator; KisTileHashTableTraits2() : m_rawPointerUsers(0) { m_context = QSBR::instance().createContext(); } ~KisTileHashTableTraits2() { QSBR::instance().destroyContext(m_context); } TileTypeSP insert(qint32 key, TileTypeSP value) { TileTypeSP::ref(&value, value.data()); TileType *result = m_map.assign(key, value.data()); + if (result) { MemoryReclaimer *tmp = new MemoryReclaimer(result); QSBR::instance().enqueue(&MemoryReclaimer::destroy, tmp); } + return TileTypeSP(result); } TileTypeSP erase(qint32 key) { - qint32 currentThreads = m_rawPointerUsers.fetchAdd(1, ConsumeRelease); - + qint32 currentThreads = m_rawPointerUsers.fetchAdd(1, Relaxed); TileType *result = m_map.erase(key); TileTypeSP ptr(result); + if (result) { MemoryReclaimer *tmp = new MemoryReclaimer(result); QSBR::instance().enqueue(&MemoryReclaimer::destroy, tmp); } qint32 expected = 1; - if (m_rawPointerUsers.compareExchangeStrong(expected, currentThreads, ConsumeRelease)) { + + if (m_rawPointerUsers.compareExchangeStrong(expected, currentThreads, Relaxed)) { QSBR::instance().update(m_context); } - m_rawPointerUsers.fetchSub(1, ConsumeRelease); + m_rawPointerUsers.fetchSub(1, Relaxed); return ptr; } TileTypeSP get(qint32 key) { - m_rawPointerUsers.fetchAdd(1, ConsumeRelease); + m_rawPointerUsers.fetchAdd(1, Relaxed); TileTypeSP result(m_map.get(key)); - m_rawPointerUsers.fetchSub(1, ConsumeRelease); + m_rawPointerUsers.fetchSub(1, Relaxed); return result; } - Iterator iterator() + TileTypeSP getLazy(qint32 key) { - return Iterator(m_map); + m_rawPointerUsers.fetchAdd(1, Relaxed); + typename ConcurrentMap::Mutator iter = m_map.insertOrFind(key); + m_rawPointerUsers.fetchSub(1, Relaxed); + + if (!iter.getValue()) { + TileTypeSP value(new TileType); + TileTypeSP::ref(&value, value.data()); + TileType *result = iter.exchangeValue(value.data()); + + if (result) { + MemoryReclaimer *tmp = new MemoryReclaimer(result); + QSBR::instance().enqueue(&MemoryReclaimer::destroy, tmp); + } + } + + return TileTypeSP(iter.getValue()); } private: struct MemoryReclaimer { MemoryReclaimer(TileType *data) : d(data) {} ~MemoryReclaimer() = default; void destroy() { TileTypeSP::deref(reinterpret_cast(this), d); this->MemoryReclaimer::~MemoryReclaimer(); std::free(this); } private: TileType *d; }; ConcurrentMap m_map; QSBR::Context m_context; Atomic m_rawPointerUsers; }; #endif // KIS_TILEHASHTABLE_2_H diff --git a/libs/image/tiles3/tests/kis_lock_free_map_test.cpp b/libs/image/tiles3/tests/kis_lock_free_map_test.cpp index 640ee4b7f7..7b808ee98a 100644 --- a/libs/image/tiles3/tests/kis_lock_free_map_test.cpp +++ b/libs/image/tiles3/tests/kis_lock_free_map_test.cpp @@ -1,121 +1,185 @@ #include "kis_lock_free_map_test.h" #include #include "kis_debug.h" #include "tiles3/kis_memento_item.h" #include "tiles3/kis_tile_hash_table2.h" -#define NUM_TYPES 3 - -// high-concurrency -#define NUM_CYCLES 60000 #define NUM_THREADS 10 class Wrapper : public KisShared { public: - Wrapper() {} + Wrapper() : m_member(0) {} Wrapper(qint32 member) : m_member(member) {} qint32 member() { return m_member; } private: qint32 m_member; }; -class StressJobWrapper : public QRunnable +class StressJob : public QRunnable { public: - StressJobWrapper(KisTileHashTableTraits2 &map) - : m_map(map), m_eraseSum(0), m_insertSum(0) + StressJob(const std::function func) + : m_func(func), m_eraseSum(0), m_insertSum(0) { } qint64 eraseSum() { return m_eraseSum; } qint64 insertSum() { return m_insertSum; } protected: void run() override { - for (qint32 i = 1; i < NUM_CYCLES + 1; ++i) { - auto type = i % NUM_TYPES; + m_func(m_eraseSum, m_insertSum); + } + +private: + const std::function m_func; + qint64 m_eraseSum; + qint64 m_insertSum; +}; + +void LockFreeMapTest::testMainOperations() +{ + const qint32 numCycles = 60000; + const qint32 numTypes = 3; + QList jobs; + KisTileHashTableTraits2 map; + + auto func = [&](qint64 &eraseSum, qint64 &insertSum) { + for (qint32 i = 1; i < numCycles + 1; ++i) { + auto type = i % numTypes; switch (type) { case 0: { - auto result = m_map.erase(i - 2); + auto result = map.erase(i - 2); if (result.data()) { - m_eraseSum += result->member(); + eraseSum += result->member(); } break; } case 1: { - auto result = m_map.insert(i, new Wrapper(i)); + auto result = map.insert(i, new Wrapper(i)); if (result.data()) { - m_insertSum -= result->member(); + insertSum -= result->member(); } - m_insertSum += i; + insertSum += i; break; } case 2: { - m_map.get(i - 1); + map.get(i - 1); break; } } } + }; + + for (qint32 i = 0; i < NUM_THREADS; ++i) { + StressJob *job = new StressJob(func); + job->setAutoDelete(false); + jobs.append(job); } -private: - KisTileHashTableTraits2 &m_map; - qint64 m_eraseSum; - qint64 m_insertSum; -}; + QThreadPool pool; + pool.setMaxThreadCount(NUM_THREADS); + + QBENCHMARK { + for (auto &job : jobs) + { + pool.start(job); + } + + pool.waitForDone(); + } + + qint64 insertSum = 0; + qint64 eraseSum = 0; + + for (qint32 i = 0; i < NUM_THREADS; ++i) { + StressJob *job = jobs.takeLast(); + eraseSum += job->eraseSum(); + insertSum += job->insertSum(); -void LockFreeMapTest::testWrapper() + delete job; + } + + QVERIFY(insertSum == eraseSum); +} + +void LockFreeMapTest::testLazy() { - QList jobs; + const qint32 numCycles = 50000; + const qint32 numTypes = 2; + QList jobs; KisTileHashTableTraits2 map; + auto func = [&](qint64 &eraseSum, qint64 &insertSum) { + for (qint32 i = 1; i < numCycles + 1; ++i) { + auto type = i % numTypes; + + switch (type) { + case 0: { + auto result = map.erase(i - 1); + if (result.data()) { + eraseSum += result->member(); + } + break; + } + case 1: { + auto result = map.getLazy(i); + if (result.data()) { + insertSum += result->member(); + } + break; + } + } + } + }; + for (qint32 i = 0; i < NUM_THREADS; ++i) { - StressJobWrapper *job = new StressJobWrapper(map); + StressJob *job = new StressJob(func); job->setAutoDelete(false); jobs.append(job); } QThreadPool pool; pool.setMaxThreadCount(NUM_THREADS); QBENCHMARK { for (auto &job : jobs) { pool.start(job); } pool.waitForDone(); } qint64 insertSum = 0; qint64 eraseSum = 0; for (qint32 i = 0; i < NUM_THREADS; ++i) { - StressJobWrapper *job = jobs.takeLast(); + StressJob *job = jobs.takeLast(); eraseSum += job->eraseSum(); insertSum += job->insertSum(); delete job; } QVERIFY(insertSum == eraseSum); } QTEST_GUILESS_MAIN(LockFreeMapTest) diff --git a/libs/image/tiles3/tests/kis_lock_free_map_test.h b/libs/image/tiles3/tests/kis_lock_free_map_test.h index 0a70bcaa4c..cdbe82cb49 100644 --- a/libs/image/tiles3/tests/kis_lock_free_map_test.h +++ b/libs/image/tiles3/tests/kis_lock_free_map_test.h @@ -1,14 +1,15 @@ #ifndef KIS_LOCK_FREE_MAP_TEST_H #define KIS_LOCK_FREE_MAP_TEST_H #include class LockFreeMapTest : public QObject { Q_OBJECT private Q_SLOTS: - void testWrapper(); + void testMainOperations(); + void testLazy(); }; #endif // KIS_LOCK_FREE_MAP_TEST_H