QmlDesigner: Cleanup storage cache

The cache was not using locking. It is now activated. If that will be a
performance problem we can add an extra path cache for the main thread.

Change-Id: I74565b4a8679a2c015ff651c3480362e924809a3
Reviewed-by: Thomas Hartmann <thomas.hartmann@qt.io>
This commit is contained in:
Marco Bubke
2025-04-02 11:57:04 +02:00
parent 204d55901c
commit 1b38d1d6cc
7 changed files with 136 additions and 82 deletions

View File

@@ -241,6 +241,71 @@ struct set_greedy_difference_functor
inline constexpr set_greedy_difference_functor set_greedy_difference{}; inline constexpr set_greedy_difference_functor set_greedy_difference{};
struct set_difference_functor
{
template<std::input_iterator Iterator1,
std::sentinel_for<Iterator1> Sentinel1,
std::input_iterator Iterator2,
std::sentinel_for<Iterator2> Sentinel2,
std::invocable<std::iter_value_t<Iterator1> &> Callable,
typename Comp = std::ranges::less,
typename Projection1 = std::identity,
typename Projection2 = std::identity>
requires callmergeable<Iterator1, Iterator2, Comp, Projection1, Projection2>
constexpr void operator()(Iterator1 first1,
Sentinel1 last1,
Iterator2 first2,
Sentinel2 last2,
Callable &&callable,
Comp comp = {},
Projection1 proj1 = {},
Projection2 proj2 = {}) const
{
while (first1 != last1 && first2 != last2) {
if (std::invoke(comp, std::invoke(proj1, *first1), std::invoke(proj2, *first2))) {
std::invoke(callable, *first1);
++first1;
} else if (std::invoke(comp, std::invoke(proj2, *first2), std::invoke(proj1, *first1))) {
++first2;
} else {
++first1;
++first2;
}
}
while (first1 != last1) {
std::invoke(callable, *first1);
++first1;
}
}
template<std::ranges::input_range Range1,
std::ranges::input_range Range2,
std::invocable<std::iter_value_t<Range1> &> Callable,
typename Comp = std::ranges::less,
typename Projection1 = std::identity,
typename Projection2 = std::identity>
requires callmergeable<std::ranges::iterator_t<Range1>, std::ranges::iterator_t<Range2>, Comp, Projection1, Projection2>
constexpr void operator()(Range1 &&range1,
Range2 &&range2,
Callable &&callable,
Comp comp = {},
Projection1 proj1 = {},
Projection2 proj2 = {}) const
{
(*this)(std::ranges::begin(range1),
std::ranges::end(range1),
std::ranges::begin(range2),
std::ranges::end(range2),
std::forward<Callable>(callable),
std::move(comp),
std::move(proj1),
std::move(proj2));
}
};
inline constexpr set_difference_functor set_difference{};
struct set_has_common_element_functor struct set_has_common_element_functor
{ {
template<std::input_iterator Iterator1, template<std::input_iterator Iterator1,

View File

@@ -9,6 +9,8 @@
#include <utils/smallstringview.h> #include <utils/smallstringview.h>
#include <utils/span.h> #include <utils/span.h>
#include <shared_mutex>
namespace QmlDesigner { namespace QmlDesigner {
using PropertyName = QByteArray; using PropertyName = QByteArray;
using PropertyNameView = QByteArrayView; using PropertyNameView = QByteArrayView;
@@ -40,7 +42,7 @@ constexpr bool useProjectStorage()
#endif #endif
} }
class SourcePathStorage; class SourcePathStorage;
using PathCache = SourcePathCache<SourcePathStorage, NonLockingMutex>; using PathCache = SourcePathCache<SourcePathStorage, std::shared_mutex>;
#ifdef QDS_MODEL_USE_PROJECTSTORAGEINTERFACE #ifdef QDS_MODEL_USE_PROJECTSTORAGEINTERFACE
using ProjectStorageType = ProjectStorageInterface; using ProjectStorageType = ProjectStorageInterface;
@@ -48,7 +50,7 @@ class SourcePathCacheInterface;
using PathCacheType = SourcePathCacheInterface; using PathCacheType = SourcePathCacheInterface;
#else #else
using ProjectStorageType = ProjectStorage; using ProjectStorageType = ProjectStorage;
using PathCacheType = SourcePathCache<SourcePathStorage, NonLockingMutex>; using PathCacheType = SourcePathCache<SourcePathStorage, std::shared_mutex>;
#endif #endif
struct ProjectStorageDependencies struct ProjectStorageDependencies

View File

@@ -284,10 +284,13 @@ private:
friend ModuleStorageAdapter; friend ModuleStorageAdapter;
static bool moduleNameLess(ModuleView first, ModuleView second) noexcept struct ModuleNameLess
{
bool operator()(ModuleView first, ModuleView second) const noexcept
{ {
return first < second; return first < second;
} }
};
class ModuleCacheEntry : public StorageCacheEntry<Storage::Module, ModuleView, ModuleId> class ModuleCacheEntry : public StorageCacheEntry<Storage::Module, ModuleView, ModuleId>
{ {
@@ -314,7 +317,7 @@ private:
using ModuleCacheEntries = std::vector<ModuleCacheEntry>; using ModuleCacheEntries = std::vector<ModuleCacheEntry>;
using ModuleCache using ModuleCache
= StorageCache<Storage::Module, ModuleView, ModuleId, ModuleStorageAdapter, NonLockingMutex, moduleNameLess, ModuleCacheEntry>; = StorageCache<Storage::Module, ModuleView, ModuleId, ModuleStorageAdapter, NonLockingMutex, ModuleNameLess, ModuleCacheEntry>;
ModuleId fetchModuleId(Utils::SmallStringView moduleName, Storage::ModuleKind moduleKind); ModuleId fetchModuleId(Utils::SmallStringView moduleName, Storage::ModuleKind moduleKind);

View File

@@ -142,27 +142,38 @@ private:
Storage &storage; Storage &storage;
}; };
static bool sourceLess(Utils::SmallStringView first, Utils::SmallStringView second) noexcept struct SourceNameLess
{ {
return std::lexicographical_compare(first.rbegin(), bool operator()(Utils::SmallStringView first, Utils::SmallStringView second) const noexcept
{
return first < second;
}
};
struct SourceContextLess
{
bool operator()(Utils::SmallStringView first, Utils::SmallStringView second) const noexcept
{
return std::ranges::lexicographical_compare(first.rbegin(),
first.rend(), first.rend(),
second.rbegin(), second.rbegin(),
second.rend()); second.rend());
} }
};
using SourceContextPathCache = StorageCache<Utils::PathString, using SourceContextPathCache = StorageCache<Utils::PathString,
Utils::SmallStringView, Utils::SmallStringView,
SourceContextId, SourceContextId,
SourceContextStorageAdapter, SourceContextStorageAdapter,
Mutex, Mutex,
sourceLess, SourceContextLess,
Cache::SourceContext>; Cache::SourceContext>;
using SourceNameCache = StorageCache<Utils::PathString, using SourceNameCache = StorageCache<Utils::SmallString,
Utils::SmallStringView, Utils::SmallStringView,
SourceNameId, SourceNameId,
SourceNameStorageAdapter, SourceNameStorageAdapter,
Mutex, Mutex,
sourceLess, SourceNameLess,
Cache::SourceName>; Cache::SourceName>;
private: private:

View File

@@ -33,7 +33,7 @@ template<typename Type,
typename IndexType, typename IndexType,
typename Storage, typename Storage,
typename Mutex, typename Mutex,
bool (*compare)(ViewType, ViewType), typename Compare,
class CacheEntry = StorageCacheEntry<Type, ViewType, IndexType>> class CacheEntry = StorageCacheEntry<Type, ViewType, IndexType>>
class StorageCache class StorageCache
{ {
@@ -140,17 +140,11 @@ public:
{ {
m_entries = m_storage.fetchAll(); m_entries = m_storage.fetchAll();
std::sort(m_entries.begin(), m_entries.end(), [](ViewType first, ViewType second) { std::ranges::sort(m_entries, Compare{});
return compare(first, second);
});
std::size_t max_id = 0; std::size_t max_id = 0;
auto found = std::max_element(m_entries.begin(), auto found = std::ranges::max_element(m_entries, {}, &CacheEntry::id);
m_entries.end(),
[](const auto &first, const auto &second) {
return first.id < second.id;
});
if (found != m_entries.end()) if (found != m_entries.end())
max_id = static_cast<std::size_t>(found->id); max_id = static_cast<std::size_t>(found->id);
@@ -162,31 +156,25 @@ public:
void add(std::vector<ViewType> &&views) void add(std::vector<ViewType> &&views)
{ {
auto less = [](ViewType first, ViewType second) { return compare(first, second); }; std::ranges::sort(views, Compare{});
std::sort(views.begin(), views.end(), less); auto removed = std::ranges::unique(views.begin(), views.end());
views.erase(removed.begin(), removed.end());
views.erase(std::unique(views.begin(), views.end()), views.end());
CacheEntries newCacheEntries; CacheEntries newCacheEntries;
newCacheEntries.reserve(views.size()); newCacheEntries.reserve(views.size());
std::set_difference(views.begin(), Utils::set_difference(
views.end(), views,
m_entries.begin(), m_entries,
m_entries.end(), [&](ViewType newView) {
Utils::make_iterator([&](ViewType newView) {
IndexType index = m_storage.fetchId(newView); IndexType index = m_storage.fetchId(newView);
newCacheEntries.emplace_back(newView, index); newCacheEntries.emplace_back(newView, index);
}), },
less); Compare{});
if (newCacheEntries.size()) { if (newCacheEntries.size()) {
auto found = std::max_element(newCacheEntries.begin(), auto found = std::ranges::max_element(newCacheEntries, {}, &CacheEntry::id);
newCacheEntries.end(),
[](const auto &first, const auto &second) {
return first.id < second.id;
});
auto max_id = static_cast<std::size_t>(found->id); auto max_id = static_cast<std::size_t>(found->id);
@@ -196,12 +184,12 @@ public:
CacheEntries mergedCacheEntries; CacheEntries mergedCacheEntries;
mergedCacheEntries.reserve(newCacheEntries.size() + m_entries.size()); mergedCacheEntries.reserve(newCacheEntries.size() + m_entries.size());
std::merge(std::make_move_iterator(m_entries.begin()), std::ranges::merge(std::make_move_iterator(m_entries.begin()),
std::make_move_iterator(m_entries.end()), std::make_move_iterator(m_entries.end()),
std::make_move_iterator(newCacheEntries.begin()), std::make_move_iterator(newCacheEntries.begin()),
std::make_move_iterator(newCacheEntries.end()), std::make_move_iterator(newCacheEntries.end()),
std::back_inserter(mergedCacheEntries), std::back_inserter(mergedCacheEntries),
less); Compare{});
m_entries = std::move(mergedCacheEntries); m_entries = std::move(mergedCacheEntries);
@@ -221,7 +209,7 @@ public:
sharedLock.unlock(); sharedLock.unlock();
std::lock_guard<Mutex> exclusiveLock(m_mutex); std::lock_guard<Mutex> exclusiveLock(m_mutex);
if (!std::is_base_of<NonLockingMutex, Mutex>::value) if constexpr (!std::is_base_of_v<NonLockingMutex, Mutex>)
found = find(view); found = find(view);
if (found == m_entries.end()) if (found == m_entries.end())
found = insertEntry(found, view, m_storage.fetchId(view)); found = insertEntry(found, view, m_storage.fetchId(view));
@@ -235,7 +223,7 @@ public:
std::vector<IndexType> ids; std::vector<IndexType> ids;
ids.reserve(values.size()); ids.reserve(values.size());
std::transform(values.begin(), values.end(), std::back_inserter(ids), [&](const auto &values) { std::ranges::transform(values, std::back_inserter(ids), [&](const auto &values) {
return this->id(values); return this->id(values);
}); });
@@ -252,9 +240,9 @@ public:
std::shared_lock<Mutex> sharedLock(m_mutex); std::shared_lock<Mutex> sharedLock(m_mutex);
if (IndexType::create(static_cast<IndexDatabaseType>(m_indices.size()) + 1) > id) { if (IndexType::create(static_cast<IndexDatabaseType>(m_indices.size()) + 1) > id) {
if (StorageCacheIndex indirectionIndex = m_indices.at(static_cast<std::size_t>(id) - 1); if (StorageCacheIndex indirectionIndex = m_indices[static_cast<std::size_t>(id) - 1];
indirectionIndex.isValid()) { indirectionIndex.isValid()) {
return m_entries.at(static_cast<std::size_t>(indirectionIndex)).value; return m_entries[static_cast<std::size_t>(indirectionIndex)].value;
} }
} }
@@ -276,9 +264,7 @@ public:
for (IndexType id : ids) { for (IndexType id : ids) {
values.emplace_back( values.emplace_back(
m_entries m_entries[static_cast<std::size_t>(m_indices[static_cast<std::size_t>(id) - 1])].value);
.at(static_cast<std::size_t>(m_indices.at(static_cast<std::size_t>(id) - 1)))
.value);
} }
return values; return values;
} }
@@ -306,19 +292,10 @@ private:
template<typename Entries> template<typename Entries>
static auto find(Entries &&entries, ViewType view) static auto find(Entries &&entries, ViewType view)
{ {
auto begin = entries.begin(); auto found = std::ranges::lower_bound(entries, view, Compare{});
auto end = entries.end();
auto found = std::lower_bound(begin, end, view, compare);
if (found == entries.end()) { if (found != entries.end() && *found == view)
return entries.end();
}
const auto &value = *found;
if (value == view) {
return found; return found;
}
return entries.end(); return entries.end();
} }
@@ -329,9 +306,8 @@ private:
auto found = find(view); auto found = find(view);
if (found != m_entries.end()) { if (found != m_entries.end())
return found->id; return found->id;
}
return IndexType(); return IndexType();
} }
@@ -341,9 +317,9 @@ private:
std::shared_lock<Mutex> sharedLock(m_mutex); std::shared_lock<Mutex> sharedLock(m_mutex);
if (IndexType::create(static_cast<IndexDatabaseType>(m_indices.size()) + 1) > id) { if (IndexType::create(static_cast<IndexDatabaseType>(m_indices.size()) + 1) > id) {
if (StorageCacheIndex indirectionIndex = m_indices.at(static_cast<std::size_t>(id) - 1); if (StorageCacheIndex indirectionIndex = m_indices[static_cast<std::size_t>(id) - 1];
indirectionIndex.isValid()) { indirectionIndex.isValid()) {
return m_entries.at(static_cast<std::size_t>(indirectionIndex)).value; return m_entries[static_cast<std::size_t>(indirectionIndex)].value;
} }
} }
@@ -377,7 +353,7 @@ private:
auto indirectionIndex = static_cast<std::size_t>(id) - 1; auto indirectionIndex = static_cast<std::size_t>(id) - 1;
ensureSize(indirectionIndex); ensureSize(indirectionIndex);
m_indices.at(indirectionIndex) = newIndirectionIndex; m_indices[indirectionIndex] = newIndirectionIndex;
return inserted; return inserted;
} }

View File

@@ -9,12 +9,6 @@ namespace QmlDesigner {
class NonLockingMutex; class NonLockingMutex;
template<typename Type, template<typename Type, typename ViewType, typename IndexType, typename Storage, typename Mutex, typename Compare, typename CacheEntry>
typename ViewType,
typename IndexType,
typename Storage,
typename Mutex,
bool (*compare)(ViewType, ViewType),
typename CacheEntry>
class StorageCache; class StorageCache;
} // namespace QmlDesigner } // namespace QmlDesigner

View File

@@ -30,9 +30,12 @@ public:
ProjectStorageMock &storage; ProjectStorageMock &storage;
}; };
auto less(Utils::SmallStringView first, Utils::SmallStringView second) -> bool struct Less
{
bool operator()(Utils::SmallStringView first, Utils::SmallStringView second) const
{ {
return Utils::compare(first, second) < 0; return Utils::compare(first, second) < 0;
}
}; };
using CacheWithMockLocking = QmlDesigner::StorageCache<Utils::PathString, using CacheWithMockLocking = QmlDesigner::StorageCache<Utils::PathString,
@@ -40,7 +43,7 @@ using CacheWithMockLocking = QmlDesigner::StorageCache<Utils::PathString,
SourceContextId, SourceContextId,
StorageAdapter, StorageAdapter,
NiceMock<MockMutex>, NiceMock<MockMutex>,
less, Less,
QmlDesigner::Cache::SourceContext>; QmlDesigner::Cache::SourceContext>;
using CacheWithoutLocking = QmlDesigner::StorageCache<Utils::PathString, using CacheWithoutLocking = QmlDesigner::StorageCache<Utils::PathString,
@@ -48,7 +51,7 @@ using CacheWithoutLocking = QmlDesigner::StorageCache<Utils::PathString,
SourceContextId, SourceContextId,
StorageAdapter, StorageAdapter,
NiceMock<MockMutexNonLocking>, NiceMock<MockMutexNonLocking>,
less, Less,
QmlDesigner::Cache::SourceContext>; QmlDesigner::Cache::SourceContext>;
template<typename Cache> template<typename Cache>