CppEditor: Speed up findFilesInProject()

This function had a number of serious performance problems. As it is
executed by several quickfix factories, it could cause freezes on right-
clicking a symbol.
Measures taken:
  - Check file type before file path.
  - Do not collect all files in the project first, but filter earlier.
  - Check all candidate file names at once.
In an example project, I observed a speed-up of factor ~100.
Some FilePath-ification was done as well.

Task-number: QTCREATORBUG-29611
Change-Id: Ic5dc48ffd86f22263d1caea4b6bfea5f49e589a4
Reviewed-by: <github-actions-qt-creator@cristianadam.eu>
Reviewed-by: David Schulz <david.schulz@qt.io>
This commit is contained in:
Christian Kandeler
2023-09-15 13:20:52 +02:00
parent 67bd5fc359
commit f1fa1ad3d9

View File

@@ -668,25 +668,20 @@ void CppEditorPlugin::setGlobalFileSettings(const CppFileSettings &settings)
} }
#endif #endif
static QStringList findFilesInProject(const QString &name, const Project *project) static FilePaths findFilesInProject(const QStringList &names, const Project *project,
FileType fileType)
{ {
if (debug) if (debug)
qDebug() << Q_FUNC_INFO << name << project; qDebug() << Q_FUNC_INFO << names << project;
if (!project) if (!project)
return {}; return {};
QString pattern = QString(1, QLatin1Char('/')); const auto filter = [&](const Node *n) {
pattern += name; const auto fn = n->asFileNode();
const QStringList projectFiles return fn && fn->fileType() == fileType && names.contains(fn->filePath().fileName());
= transform(project->files(Project::AllFiles), &FilePath::toString); };
const QStringList::const_iterator pcend = projectFiles.constEnd(); return project->files(filter);
QStringList candidateList;
for (QStringList::const_iterator it = projectFiles.constBegin(); it != pcend; ++it) {
if (it->endsWith(pattern, HostOsInfo::fileNameCaseSensitivity()))
candidateList.append(*it);
}
return candidateList;
} }
// Return the suffixes that should be checked when trying to find a // Return the suffixes that should be checked when trying to find a
@@ -778,30 +773,31 @@ static int commonFilePathLength(const QString &s1, const QString &s2)
static FilePath correspondingHeaderOrSourceInProject(const QFileInfo &fileInfo, static FilePath correspondingHeaderOrSourceInProject(const QFileInfo &fileInfo,
const QStringList &candidateFileNames, const QStringList &candidateFileNames,
const Project *project, const Project *project,
FileType fileType,
CacheUsage cacheUsage) CacheUsage cacheUsage)
{ {
QString bestFileName;
int compareValue = 0;
const QString filePath = fileInfo.filePath(); const QString filePath = fileInfo.filePath();
for (const QString &candidateFileName : candidateFileNames) { const FilePaths projectFiles = findFilesInProject(candidateFileNames, project, fileType);
const QStringList projectFiles = findFilesInProject(candidateFileName, project);
// Find the file having the most common path with fileName // Find the file having the most common path with fileName
for (const QString &projectFile : projectFiles) { FilePath bestFilePath;
int value = commonFilePathLength(filePath, projectFile); int compareValue = 0;
for (const FilePath &projectFile : projectFiles) {
int value = commonFilePathLength(filePath, projectFile.toString());
if (value > compareValue) { if (value > compareValue) {
compareValue = value; compareValue = value;
bestFileName = projectFile; bestFilePath = projectFile;
} }
} }
} if (!bestFilePath.isEmpty()) {
if (!bestFileName.isEmpty()) { QTC_ASSERT(bestFilePath.isFile(), return {});
const QFileInfo candidateFi(bestFileName);
QTC_ASSERT(candidateFi.isFile(), return {});
if (cacheUsage == CacheUsage::ReadWrite) { if (cacheUsage == CacheUsage::ReadWrite) {
m_headerSourceMapping[fileInfo.absoluteFilePath()] = candidateFi.absoluteFilePath(); m_headerSourceMapping[fileInfo.absoluteFilePath()]
m_headerSourceMapping[candidateFi.absoluteFilePath()] = fileInfo.absoluteFilePath(); = bestFilePath.absoluteFilePath().toString();
m_headerSourceMapping[bestFilePath.absoluteFilePath().toString()]
= fileInfo.absoluteFilePath();
} }
return FilePath::fromString(candidateFi.absoluteFilePath()); return bestFilePath;
} }
return {}; return {};
@@ -878,9 +874,10 @@ FilePath correspondingHeaderOrSource(const FilePath &filePath, bool *wasHeader,
Project *currentProject = projectForFile; Project *currentProject = projectForFile;
if (!projectForFile) if (!projectForFile)
currentProject = ProjectTree::currentProject(); currentProject = ProjectTree::currentProject();
const FileType requestedFileType = isHeader ? FileType::Source : FileType::Header;
if (currentProject) { if (currentProject) {
const FilePath path = correspondingHeaderOrSourceInProject(fi, candidateFileNames, const FilePath path = correspondingHeaderOrSourceInProject(
currentProject, cacheUsage); fi, candidateFileNames, currentProject, requestedFileType, cacheUsage);
if (!path.isEmpty()) if (!path.isEmpty())
return path; return path;
@@ -892,8 +889,8 @@ FilePath correspondingHeaderOrSource(const FilePath &filePath, bool *wasHeader,
if (project == currentProject) if (project == currentProject)
continue; // We have already checked the current project. continue; // We have already checked the current project.
const FilePath path = correspondingHeaderOrSourceInProject(fi, candidateFileNames, const FilePath path = correspondingHeaderOrSourceInProject(
project, cacheUsage); fi, candidateFileNames, project, requestedFileType, cacheUsage);
if (!path.isEmpty()) if (!path.isEmpty())
return path; return path;
} }