diff --git a/libs/image/tiles3/kis_tile_data_store.cc b/libs/image/tiles3/kis_tile_data_store.cc index bd0405a428..493756b2ec 100644 --- a/libs/image/tiles3/kis_tile_data_store.cc +++ b/libs/image/tiles3/kis_tile_data_store.cc @@ -1,374 +1,377 @@ /* * Copyright (c) 2009 Dmitry Kazakov * Copyright (c) 2018 Andrey Kamakin * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ // to disable assert when the leak tracker is active #include "config-memory-leak-tracker.h" #include #include "kis_tile_data_store.h" #include "kis_tile_data.h" #include "kis_debug.h" #include "kis_tile_data_store_iterators.h" Q_GLOBAL_STATIC(KisTileDataStore, s_instance) //#define DEBUG_PRECLONE #ifdef DEBUG_PRECLONE #include #define DEBUG_PRECLONE_ACTION(action, oldTD, newTD) \ printf("!!! %s:\t\t\t 0x%X -> 0x%X \t\t!!!\n", \ action, (quintptr)oldTD, (quintptr) newTD) #define DEBUG_FREE_ACTION(td) \ printf("Tile data free'd \t(0x%X)\n", td) #else #define DEBUG_PRECLONE_ACTION(action, oldTD, newTD) #define DEBUG_FREE_ACTION(td) #endif #ifdef DEBUG_HIT_MISS qint64 __preclone_miss = 0; qint64 __preclone_hit = 0; qint64 __preclone_miss_user_count = 0; qint64 __preclone_miss_age = 0; #define DEBUG_COUNT_PRECLONE_HIT(td) __preclone_hit++ #define DEBUG_COUNT_PRECLONE_MISS(td) __preclone_miss++; __preclone_miss_user_count+=td->numUsers(); __preclone_miss_age+=td->age() #define DEBUG_REPORT_PRECLONE_EFFICIENCY() \ dbgKrita << "Hits:" << __preclone_hit \ << "of" << __preclone_hit + __preclone_miss \ << "(" \ << qreal(__preclone_hit) / (__preclone_hit + __preclone_miss) \ << ")" \ << "miss users" << qreal(__preclone_miss_user_count) / __preclone_miss \ << "miss age" << qreal(__preclone_miss_age) / __preclone_miss #else #define DEBUG_COUNT_PRECLONE_HIT(td) #define DEBUG_COUNT_PRECLONE_MISS(td) #define DEBUG_REPORT_PRECLONE_EFFICIENCY() #endif KisTileDataStore::KisTileDataStore() : m_pooler(this), m_swapper(this), m_numTiles(0), m_memoryMetric(0), m_counter(1), m_clockIndex(1) { m_pooler.start(); m_swapper.start(); } KisTileDataStore::~KisTileDataStore() { m_pooler.terminatePooler(); m_swapper.terminateSwapper(); if (numTiles() > 0) { errKrita << "Warning: some tiles have leaked:"; errKrita << "\tTiles in memory:" << numTilesInMemory() << "\n" << "\tTotal tiles:" << numTiles(); } } KisTileDataStore* KisTileDataStore::instance() { return s_instance; } KisTileDataStore::MemoryStatistics KisTileDataStore::memoryStatistics() { // in case the pooler is disabled, we should force it // to update the stats if (!m_pooler.isRunning()) { m_pooler.forceUpdateMemoryStats(); } QReadLocker lock(&m_iteratorLock); MemoryStatistics stats; const qint64 metricCoeff = KisTileData::WIDTH * KisTileData::HEIGHT; stats.realMemorySize = m_pooler.lastRealMemoryMetric() * metricCoeff; stats.historicalMemorySize = m_pooler.lastHistoricalMemoryMetric() * metricCoeff; stats.poolSize = m_pooler.lastPoolMemoryMetric() * metricCoeff; stats.totalMemorySize = memoryMetric() * metricCoeff + stats.poolSize; stats.swapSize = m_swappedStore.totalMemoryMetric() * metricCoeff; return stats; } inline void KisTileDataStore::registerTileDataImp(KisTileData *td) { int index = m_counter.fetchAndAddOrdered(1); td->m_tileNumber = index; m_tileDataMap.assign(index, td); m_numTiles.ref(); m_memoryMetric += td->pixelSize(); } void KisTileDataStore::registerTileData(KisTileData *td) { QReadLocker lock(&m_iteratorLock); registerTileDataImp(td); } inline void KisTileDataStore::unregisterTileDataImp(KisTileData *td) { if (m_clockIndex == td->m_tileNumber) { do { m_clockIndex.ref(); } while (!m_tileDataMap.get(m_clockIndex.loadAcquire()) && m_clockIndex < m_counter); } int index = td->m_tileNumber; td->m_tileNumber = -1; m_tileDataMap.erase(index); m_numTiles.deref(); m_memoryMetric -= td->pixelSize(); } void KisTileDataStore::unregisterTileData(KisTileData *td) { QReadLocker lock(&m_iteratorLock); unregisterTileDataImp(td); } KisTileData *KisTileDataStore::allocTileData(qint32 pixelSize, const quint8 *defPixel) { KisTileData *td = new KisTileData(pixelSize, defPixel, this); registerTileData(td); return td; } KisTileData *KisTileDataStore::duplicateTileData(KisTileData *rhs) { KisTileData *td = 0; if (rhs->m_clonesStack.pop(td)) { DEBUG_PRECLONE_ACTION("+ Pre-clone HIT", rhs, td); DEBUG_COUNT_PRECLONE_HIT(rhs); } else { rhs->blockSwapping(); td = new KisTileData(*rhs); rhs->unblockSwapping(); DEBUG_PRECLONE_ACTION("- Pre-clone #MISS#", rhs, td); DEBUG_COUNT_PRECLONE_MISS(rhs); } registerTileData(td); return td; } void KisTileDataStore::freeTileData(KisTileData *td) { Q_ASSERT(td->m_store == this); DEBUG_FREE_ACTION(td); m_iteratorLock.lockForRead(); td->m_swapLock.lockForWrite(); if (!td->data()) { m_swappedStore.forgetTileData(td); } else { unregisterTileDataImp(td); } td->m_swapLock.unlock(); m_iteratorLock.unlock(); delete td; } void KisTileDataStore::ensureTileDataLoaded(KisTileData *td) { // dbgKrita << "#### SWAP MISS! ####" << td << ppVar(td->mementoed()) << ppVar(td->age()) << ppVar(td->numUsers()); checkFreeMemory(); td->m_swapLock.lockForRead(); while (!td->data()) { td->m_swapLock.unlock(); /** * The order of this heavy locking is very important. * Change it only in case, you really know what you are doing. */ - m_iteratorLock.lockForRead(); + m_iteratorLock.lockForWrite(); /** * If someone has managed to load the td from swap, then, most * probably, they have already taken the swap lock. This may * lead to a deadlock, because COW mechanism breaks lock * ordering rules in duplicateTileData() (it takes m_listLock * while the swap lock is held). In our case it is enough just * to check whether the other thread has already fetched the * data. Please notice that we do not take both of the locks * while checking this, because holding m_listLock is * enough. Nothing can happen to the tile while we hold * m_listLock. */ if (!td->data()) { td->m_swapLock.lockForWrite(); m_swappedStore.swapInTileData(td); registerTileDataImp(td); td->m_swapLock.unlock(); } m_iteratorLock.unlock(); /** * <-- In theory, livelock is possible here... */ td->m_swapLock.lockForRead(); } } bool KisTileDataStore::trySwapTileData(KisTileData *td) { /** * This function is called with m_listLock acquired */ bool result = false; if (!td->m_swapLock.tryLockForWrite()) return result; if (td->data()) { unregisterTileDataImp(td); if (m_swappedStore.trySwapOutTileData(td)) { result = true; } else { result = false; registerTileDataImp(td); } } td->m_swapLock.unlock(); return result; } KisTileDataStoreIterator* KisTileDataStore::beginIteration() { m_iteratorLock.lockForWrite(); return new KisTileDataStoreIterator(m_tileDataMap, this); } void KisTileDataStore::endIteration(KisTileDataStoreIterator* iterator) { delete iterator; m_iteratorLock.unlock(); } KisTileDataStoreReverseIterator* KisTileDataStore::beginReverseIteration() { m_iteratorLock.lockForWrite(); return new KisTileDataStoreReverseIterator(m_tileDataMap, this); } void KisTileDataStore::endIteration(KisTileDataStoreReverseIterator* iterator) { delete iterator; m_iteratorLock.unlock(); DEBUG_REPORT_PRECLONE_EFFICIENCY(); } KisTileDataStoreClockIterator* KisTileDataStore::beginClockIteration() { m_iteratorLock.lockForWrite(); return new KisTileDataStoreClockIterator(m_tileDataMap, m_clockIndex.loadAcquire(), this); } void KisTileDataStore::endIteration(KisTileDataStoreClockIterator* iterator) { m_clockIndex = iterator->getFinalPosition(); delete iterator; m_iteratorLock.unlock(); } void KisTileDataStore::debugPrintList() { - QWriteLocker l(&m_iteratorLock); - ConcurrentMap::Iterator iter(m_tileDataMap); + KisTileDataStoreIterator* iter = beginIteration(); KisTileData *item = 0; - while (iter.isValid()) { - item = iter.getValue(); + while (iter->hasNext()) { + item = iter->next(); dbgTiles << "-------------------------\n" << "TileData:\t\t\t" << item << "\n refCount:\t" << item->m_refCount; } + + endIteration(iter); } void KisTileDataStore::debugSwapAll() { KisTileDataStoreIterator* iter = beginIteration(); - KisTileData *item; + KisTileData *item = 0; + while (iter->hasNext()) { item = iter->next(); iter->trySwapOut(item); } + endIteration(iter); // dbgKrita << "Number of tiles:" << numTiles(); // dbgKrita << "Tiles in memory:" << numTilesInMemory(); // m_swappedStore.debugStatistics(); } void KisTileDataStore::debugClear() { QWriteLocker l(&m_iteratorLock); ConcurrentMap::Iterator iter(m_tileDataMap); while (iter.isValid()) { delete iter.getValue(); iter.next(); } m_counter = 1; m_clockIndex = 1; m_numTiles = 0; m_memoryMetric = 0; } void KisTileDataStore::testingRereadConfig() { m_pooler.testingRereadConfig(); m_swapper.testingRereadConfig(); kickPooler(); } void KisTileDataStore::testingSuspendPooler() { m_pooler.terminatePooler(); } void KisTileDataStore::testingResumePooler() { m_pooler.start(); } diff --git a/libs/image/tiles3/kis_tile_hash_table2.h b/libs/image/tiles3/kis_tile_hash_table2.h index 482aa67cdb..c1abcd7db3 100644 --- a/libs/image/tiles3/kis_tile_hash_table2.h +++ b/libs/image/tiles3/kis_tile_hash_table2.h @@ -1,410 +1,410 @@ /* * Copyright (c) 2018 Andrey Kamakin * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #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" #include "kis_tile.h" #define SANITY_CHECK /** * This is a template for a hash table that stores tiles (or some other * objects resembling tiles). Actually, this object should only have * col()/row() methods and be able to answer setNext()/next() requests to * be stored here. It is used in KisTiledDataManager and * KisMementoManager. * * How to use: * 1) each hash must be unique, otherwise tiles would rewrite each-other * 2) 0 key is reserved, so can't be used * 3) col and row must be less than 0x7FFF to guarantee uniqueness of hash for each pair */ template class KisTileHashTableIteratorTraits2; template class KisTileHashTableTraits2 { 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; KisTileHashTableTraits2(KisMementoManager *mm); KisTileHashTableTraits2(const KisTileHashTableTraits2 &ht, KisMementoManager *mm); ~KisTileHashTableTraits2(); bool isEmpty() { return !m_numTiles.load(); } bool tileExists(qint32 col, qint32 row); /** * Returns a tile in position (col,row). If no tile exists, * returns null. * \param col column of the tile * \param row row of the tile */ TileTypeSP getExistingTile(qint32 col, qint32 row); /** * Returns a tile in position (col,row). If no tile exists, * creates a new one, attaches it to the list and returns. * \param col column of the tile * \param row row of the tile * \param newTile out-parameter, returns true if a new tile * was created */ TileTypeSP getTileLazy(qint32 col, qint32 row, bool& newTile); /** * Returns a tile in position (col,row). If no tile exists, * creates nothing, but returns shared default tile object * of the table. Be careful, this object has column and row * parameters set to (qint32_MIN, qint32_MIN). * \param col column of the tile * \param row row of the tile * \param existingTile returns true if the tile actually exists in the table * and it is not a lazily created default wrapper tile */ TileTypeSP getReadOnlyTileLazy(qint32 col, qint32 row, bool &existingTile); void addTile(TileTypeSP tile); bool deleteTile(TileTypeSP tile); bool deleteTile(qint32 col, qint32 row); void clear(); void setDefaultTileData(KisTileData *defaultTileData); KisTileData* defaultTileData(); qint32 numTiles() { return m_numTiles.load(); } void debugPrintInfo(); void debugMaxListLength(qint32 &min, qint32 &max); friend class KisTileHashTableIteratorTraits2; private: struct MemoryReclaimer { MemoryReclaimer(TileType *data) : d(data) {} void destroy() { TileTypeSP::deref(reinterpret_cast(this), d); this->MemoryReclaimer::~MemoryReclaimer(); delete this; } private: TileType *d; }; inline quint32 calculateHash(qint32 col, qint32 row) { #ifdef SANITY_CHECK KIS_ASSERT_RECOVER_NOOP(row < 0x7FFF && col < 0x7FFF) #endif // SANITY_CHECK if (col == 0 && row == 0) { col = 0x7FFF; row = 0x7FFF; } return ((static_cast(row) << 16) | (static_cast(col) & 0xFFFF)); } inline void insert(quint32 key, TileTypeSP value) { QReadLocker l(&m_iteratorLock); TileTypeSP::ref(&value, value.data()); TileType *result = m_map.assign(key, value.data()); if (result) { m_map.getGC().enqueue(&MemoryReclaimer::destroy, new MemoryReclaimer(result)); } else { m_numTiles.fetchAndAddRelaxed(1); } m_map.getGC().update(m_map.migrationInProcess()); } inline bool erase(quint32 key) { bool wasDeleted = false; TileType *result = m_map.erase(key); if (result) { wasDeleted = true; result->notifyDead(); m_numTiles.fetchAndSubRelaxed(1); m_map.getGC().enqueue(&MemoryReclaimer::destroy, new MemoryReclaimer(result)); } m_map.getGC().update(m_map.migrationInProcess()); return wasDeleted; } private: ConcurrentMap m_map; /** * We still need something to guard changes in m_defaultTileData, * otherwise there will be concurrent read/writes, resulting in broken memory. */ QReadWriteLock m_defaultPixelDataLock; QReadWriteLock m_iteratorLock; QAtomicInt m_numTiles; KisTileData *m_defaultTileData; KisMementoManager *m_mementoManager; }; template class KisTileHashTableIteratorTraits2 { public: typedef T TileType; typedef KisSharedPtr TileTypeSP; typedef typename ConcurrentMap::Iterator Iterator; KisTileHashTableIteratorTraits2(KisTileHashTableTraits2 *ht) : m_ht(ht) { m_ht->m_iteratorLock.lockForWrite(); m_iter.setMap(m_ht->m_map); } ~KisTileHashTableIteratorTraits2() { m_ht->m_iteratorLock.unlock(); } void next() { m_iter.next(); } TileTypeSP tile() const { return TileTypeSP(m_iter.getValue()); } bool isDone() const { return !m_iter.isValid(); } void deleteCurrent() { m_ht->erase(m_iter.getKey()); next(); } void moveCurrentToHashTable(KisTileHashTableTraits2 *newHashTable) { TileTypeSP tile = m_iter.getValue(); - newHashTable->addTile(tile); - m_ht->deleteTile(tile); next(); + m_ht->deleteTile(tile); + newHashTable->addTile(tile); } private: KisTileHashTableTraits2 *m_ht; Iterator m_iter; }; template KisTileHashTableTraits2::KisTileHashTableTraits2(KisMementoManager *mm) : m_numTiles(0), m_defaultTileData(0), m_mementoManager(mm) { } template KisTileHashTableTraits2::KisTileHashTableTraits2(const KisTileHashTableTraits2 &ht, KisMementoManager *mm) : KisTileHashTableTraits2(mm) { setDefaultTileData(ht.m_defaultTileData); QWriteLocker l(const_cast(&ht.m_iteratorLock)); typename ConcurrentMap::Iterator iter(const_cast &>(ht.m_map)); while (iter.isValid()) { insert(iter.getKey(), iter.getValue()); iter.next(); } } template KisTileHashTableTraits2::~KisTileHashTableTraits2() { clear(); m_map.getGC().flush(); setDefaultTileData(0); } template bool KisTileHashTableTraits2::tileExists(qint32 col, qint32 row) { return getExistingTile(col, row); } template typename KisTileHashTableTraits2::TileTypeSP KisTileHashTableTraits2::getExistingTile(qint32 col, qint32 row) { quint32 idx = calculateHash(col, row); TileTypeSP result = m_map.get(idx); m_map.getGC().update(m_map.migrationInProcess()); return result; } template typename KisTileHashTableTraits2::TileTypeSP KisTileHashTableTraits2::getTileLazy(qint32 col, qint32 row, bool &newTile) { QReadLocker l(&m_iteratorLock); newTile = false; TileTypeSP tile; quint32 idx = calculateHash(col, row); typename ConcurrentMap::Mutator mutator = m_map.insertOrFind(idx); if (!mutator.getValue()) { { QReadLocker guard(&m_defaultPixelDataLock); tile = new TileType(col, row, m_defaultTileData, m_mementoManager); } TileTypeSP::ref(&tile, tile.data()); TileType *result = mutator.exchangeValue(tile.data()); if (result) { m_map.getGC().enqueue(&MemoryReclaimer::destroy, new MemoryReclaimer(result)); } else { newTile = true; m_numTiles.fetchAndAddRelaxed(1); } tile = m_map.get(idx); } else { tile = mutator.getValue(); } m_map.getGC().update(m_map.migrationInProcess()); return tile; } template typename KisTileHashTableTraits2::TileTypeSP KisTileHashTableTraits2::getReadOnlyTileLazy(qint32 col, qint32 row, bool &existingTile) { quint32 idx = calculateHash(col, row); TileTypeSP tile = m_map.get(idx); existingTile = tile; if (!existingTile) { QReadLocker guard(&m_defaultPixelDataLock); tile = new TileType(col, row, m_defaultTileData, 0); } m_map.getGC().update(m_map.migrationInProcess()); return tile; } template void KisTileHashTableTraits2::addTile(TileTypeSP tile) { quint32 idx = calculateHash(tile->col(), tile->row()); insert(idx, tile); } template bool KisTileHashTableTraits2::deleteTile(TileTypeSP tile) { return deleteTile(tile->col(), tile->row()); } template bool KisTileHashTableTraits2::deleteTile(qint32 col, qint32 row) { quint32 idx = calculateHash(col, row); return erase(idx); } template void KisTileHashTableTraits2::clear() { QWriteLocker l(&m_iteratorLock); typename ConcurrentMap::Iterator iter(m_map); TileType *tile = 0; while (iter.isValid()) { tile = m_map.erase(iter.getKey()); tile->notifyDead(); m_map.getGC().enqueue(&MemoryReclaimer::destroy, new MemoryReclaimer(tile)); iter.next(); } m_numTiles.store(0); m_map.getGC().update(false); } template inline void KisTileHashTableTraits2::setDefaultTileData(KisTileData *defaultTileData) { QWriteLocker guard(&m_defaultPixelDataLock); if (m_defaultTileData) { m_defaultTileData->release(); m_defaultTileData = 0; } if (defaultTileData) { defaultTileData->acquire(); m_defaultTileData = defaultTileData; } } template inline KisTileData* KisTileHashTableTraits2::defaultTileData() { QReadLocker guard(&m_defaultPixelDataLock); return m_defaultTileData; } template void KisTileHashTableTraits2::debugPrintInfo() { } template void KisTileHashTableTraits2::debugMaxListLength(qint32 &min, qint32 &max) { } typedef KisTileHashTableTraits2 KisTileHashTable; typedef KisTileHashTableIteratorTraits2 KisTileHashTableIterator; typedef KisTileHashTableIteratorTraits2 KisTileHashTableConstIterator; #endif // KIS_TILEHASHTABLE_2_H