From 6907ec3155f5bceca421fc4228dd6cadbff60c0f Mon Sep 17 00:00:00 2001 From: Qoole <2862661+qoole@users.noreply.github.com> Date: Tue, 26 May 2026 20:21:50 +0100 Subject: [PATCH 1/2] perf(discovery): avoid per-call QTimer heap allocation when scheduling jobs Replace the 5 QTimer::singleShot(0, ...) calls that defer DiscoveryPhase::scheduleMoreJobs with QMetaObject::invokeMethod(Qt::QueuedConnection). Both achieve the same deferred (queued) invocation, but invokeMethod does not allocate a QTimer on the heap for each call. In a large sync with many directories this eliminates thousands of unnecessary heap allocations. Signed-off-by: Qoole <2862661+qoole@users.noreply.github.com> --- src/libsync/discovery.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/libsync/discovery.cpp b/src/libsync/discovery.cpp index 394a7b22fd2bb..e16bfce0b8864 100644 --- a/src/libsync/discovery.cpp +++ b/src/libsync/discovery.cpp @@ -249,7 +249,7 @@ void ProcessDirectoryJob::process() processFile(std::move(path), e.localEntry, e.serverEntry, e.dbEntry); } _discoveryData->_listExclusiveFiles.clear(); - QTimer::singleShot(0, _discoveryData, &DiscoveryPhase::scheduleMoreJobs); + QMetaObject::invokeMethod(_discoveryData, &DiscoveryPhase::scheduleMoreJobs, Qt::QueuedConnection); } bool ProcessDirectoryJob::handleExcluded(const QString &path, const Entries &entries, const std::map &allEntries, const bool isHidden, const bool isBlacklisted) @@ -698,7 +698,7 @@ void ProcessDirectoryJob::postProcessServerNew(const SyncFileItemPtr &item, if (!result) { processFileAnalyzeLocalInfo(item, path, localEntry, serverEntry, dbEntry, _queryServer); } - QTimer::singleShot(0, _discoveryData, &DiscoveryPhase::scheduleMoreJobs); + QMetaObject::invokeMethod(_discoveryData, &DiscoveryPhase::scheduleMoreJobs, Qt::QueuedConnection); }); return; } @@ -1062,7 +1062,7 @@ void ProcessDirectoryJob::processFileAnalyzeRemoteInfo(const SyncFileItemPtr &it const auto job = new RequestEtagJob(_discoveryData->_account, _discoveryData->_remoteFolder + originalPath, this); connect(job, &RequestEtagJob::finishedWithResult, this, [=, this](const HttpResult &etag) mutable { _pendingAsyncJobs--; - QTimer::singleShot(0, _discoveryData, &DiscoveryPhase::scheduleMoreJobs); + QMetaObject::invokeMethod(_discoveryData, &DiscoveryPhase::scheduleMoreJobs, Qt::QueuedConnection); if (etag || etag.error().code != 404 || // Somehow another item claimed this original path, consider as if it existed _discoveryData->isRenamed(originalPath)) { @@ -1713,7 +1713,7 @@ void ProcessDirectoryJob::processFileAnalyzeLocalInfo( } processFileFinalize(item, path, item->isDirectory(), NormalQuery, recurseQueryServer); _pendingAsyncJobs--; - QTimer::singleShot(0, _discoveryData, &DiscoveryPhase::scheduleMoreJobs); + QMetaObject::invokeMethod(_discoveryData, &DiscoveryPhase::scheduleMoreJobs, Qt::QueuedConnection); }); job->start(); return; @@ -2193,7 +2193,7 @@ void ProcessDirectoryJob::subJobFinished() int count = _runningJobs.removeAll(job); ASSERT(count == 1); job->deleteLater(); - QTimer::singleShot(0, _discoveryData, &DiscoveryPhase::scheduleMoreJobs); + QMetaObject::invokeMethod(_discoveryData, &DiscoveryPhase::scheduleMoreJobs, Qt::QueuedConnection); } int ProcessDirectoryJob::processSubJobs(int nbJobs) From db670cbf27fefbf67d020d23c51118a48bb4acc5 Mon Sep 17 00:00:00 2001 From: Qoole <2862661+qoole@users.noreply.github.com> Date: Tue, 26 May 2026 20:22:09 +0100 Subject: [PATCH 2/2] perf(discovery): use QStringView for forbidden-name checks Replace QString::split('.') with indexOf/lastIndexOf to extract the basename and extension without allocating a QList per file, and hold the slices as QStringView so the comparisons against forbiddenFilenames / forbiddenBasenames / forbiddenExtensions run against zero-copy views into the original QString instead of allocating new QString copies. Signed-off-by: Qoole <2862661+qoole@users.noreply.github.com> --- src/libsync/discovery.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/libsync/discovery.cpp b/src/libsync/discovery.cpp index e16bfce0b8864..f28d282b6aa64 100644 --- a/src/libsync/discovery.cpp +++ b/src/libsync/discovery.cpp @@ -303,9 +303,10 @@ bool ProcessDirectoryJob::handleExcluded(const QString &path, const Entries &ent } const auto &localName = entries.localEntry.name; - const auto splitName = localName.split('.'); - const auto &baseName = splitName.first(); - const auto extension = splitName.size() > 1 ? splitName.last() : QString(); + const auto firstDotIndex = localName.indexOf(QLatin1Char('.')); + const auto lastDotIndex = localName.lastIndexOf(QLatin1Char('.')); + const auto baseName = firstDotIndex >= 0 ? QStringView(localName).left(firstDotIndex) : QStringView(localName); + const auto extension = lastDotIndex >= 0 ? QStringView(localName).mid(lastDotIndex + 1) : QStringView(); const auto accountCaps = _discoveryData->_account->capabilities(); const auto forbiddenFilenames = accountCaps.forbiddenFilenames(); const auto forbiddenBasenames = accountCaps.forbiddenFilenameBasenames();