From ddc8712f93159a7d702b2a39c64c148be59906d9 Mon Sep 17 00:00:00 2001 From: liuwenhuan Date: Mon, 9 Nov 2026 09:19:50 +0800 Subject: [PATCH] perf: parse rar and 7z listings faster MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. Opening a 5-volume RAR archive with 3542 entries (38387 listing lines) took about 16 s of CPU in the GUI thread. 2. CliRarPlugin::readListLine() built and JIT-compiled the version-line regular expression for every line; it is now static and only matched until the version line is found. 3. Both plugins parsed every entry time with QDateTime::fromString(text, "yyyy-MM-dd hh:mm:ss"), whose format parser costs tens of microseconds per call. The time is now split into QDate/QTime when it has exactly that form (same local-time result); anything else still goes through fromString. Log: Open large RAR and 7z archives several times faster. Influence: 1. Parsing that listing (Qt 6.8.2): 5.2 s -> 0.58 s with TZ=Asia/Shanghai, 2.8 s -> 0.19 s in UTC; the same time for every entry. 2. A 3542-entry RAR archive loads in 0.9 s instead of 5.5 s; the file list shows the same modification times. perf: 加快 rar 和 7z 列表解析 1. 打开一个 5 卷、3542 个条目(38387 行列表输出)的 RAR 包,界面线程 要花约 16 秒 CPU。 2. CliRarPlugin::readListLine() 每读一行都重新创建并 JIT 编译版本行的正则, 改为静态对象,且只在找到版本行之前匹配。 3. 两个插件都用 QDateTime::fromString(text, "yyyy-MM-dd hh:mm:ss") 解析每个 条目的时间,格式解析每次要几十微秒。时间严格是这个格式时改为拆成 QDate/QTime(本地时间结果相同),其他情况仍走 fromString。 Log: 打开大型 RAR、7z 包快数倍。 Influence: 1. 解析上述列表(Qt 6.8.2):上海时区 5.2 秒 -> 0.58 秒,UTC 2.8 秒 -> 0.19 秒, 每个条目的时间一致。 2. 3542 个条目的 RAR 包加载从 5.5 秒降到 0.9 秒,文件列表显示的修改时间不变。 --- 3rdparty/cli7zplugin/cli7zplugin.cpp | 25 +++++++++++++++++++---- 3rdparty/clirarplugin/clirarplugin.cpp | 28 +++++++++++++++++++++----- 2 files changed, 44 insertions(+), 9 deletions(-) diff --git a/3rdparty/cli7zplugin/cli7zplugin.cpp b/3rdparty/cli7zplugin/cli7zplugin.cpp index e9cc4a9e2..cf861c0b1 100644 --- a/3rdparty/cli7zplugin/cli7zplugin.cpp +++ b/3rdparty/cli7zplugin/cli7zplugin.cpp @@ -178,6 +178,23 @@ void Cli7zPlugin::killProcess(bool emitFinished) m_isProcessKilled = true; } +// The "yyyy-MM-dd hh:mm:ss" local time of a listing line. Same result as +// QDateTime::fromString(text, "yyyy-MM-dd hh:mm:ss"), whose format parser costs tens of microseconds +// per call (seconds for an archive with thousands of entries); anything else still goes through it. +static QDateTime listTimeFromString(const QString &text) +{ + if (text.size() == 19 && text.at(4) == QLatin1Char('-') && text.at(7) == QLatin1Char('-') + && text.at(10) == QLatin1Char(' ') && text.at(13) == QLatin1Char(':') && text.at(16) == QLatin1Char(':')) { + bool ok[6] = {}; + const QDate date(text.mid(0, 4).toInt(&ok[0]), text.mid(5, 2).toInt(&ok[1]), text.mid(8, 2).toInt(&ok[2])); + const QTime time(text.mid(11, 2).toInt(&ok[3]), text.mid(14, 2).toInt(&ok[4]), text.mid(17, 2).toInt(&ok[5])); + if (ok[0] && ok[1] && ok[2] && ok[3] && ok[4] && ok[5] && date.isValid() && time.isValid()) { + return QDateTime(date, time); + } + } + return QDateTime::fromString(text, QStringLiteral("yyyy-MM-dd hh:mm:ss")); +} + bool Cli7zPlugin::readListLine(const QString &line) { ArchiveData &stArchiveData = DataManager::get_instance().archiveData(); @@ -193,7 +210,7 @@ bool Cli7zPlugin::readListLine(const QString &line) return false; } - const QRegularExpression rxVersionLine(QStringLiteral("^(?:p7zip Version|7-Zip) ([\\d\\.]+).*$")); + static const QRegularExpression rxVersionLine(QStringLiteral("^(?:p7zip Version|7-Zip) ([\\d\\.]+).*$")); QRegularExpressionMatch matchVersion; switch (m_parseState) { @@ -271,11 +288,11 @@ bool Cli7zPlugin::readListLine(const QString &line) if (dotIndex != -1) { modifiedTime.truncate(dotIndex); } - m_fileEntry.uLastModifiedTime = QDateTime::fromString(modifiedTime, + m_fileEntry.uLastModifiedTime = listTimeFromString(modifiedTime) #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) - QStringLiteral("yyyy-MM-dd hh:mm:ss")).toTime_t(); + .toTime_t(); #else - QStringLiteral("yyyy-MM-dd hh:mm:ss")).toSecsSinceEpoch(); + .toSecsSinceEpoch(); #endif if (ArchiveTypeIso == m_archiveType || ArchiveTypeUdf == m_archiveType) { // 读取的压缩包是iso、udf文件,读到Modified的时候已经结束了 // QString name = m_fileEntry.strFullPath; diff --git a/3rdparty/clirarplugin/clirarplugin.cpp b/3rdparty/clirarplugin/clirarplugin.cpp index 61f4ccec7..c48ca876b 100644 --- a/3rdparty/clirarplugin/clirarplugin.cpp +++ b/3rdparty/clirarplugin/clirarplugin.cpp @@ -26,6 +26,7 @@ #include "datamanager.h" #include +#include #include #include #include @@ -175,17 +176,35 @@ void CliRarPlugin::killProcess(bool emitFinished) m_isProcessKilled = true; } +// The "yyyy-MM-dd hh:mm:ss" local time of a listing line. Same result as +// QDateTime::fromString(text, "yyyy-MM-dd hh:mm:ss"), whose format parser costs tens of microseconds +// per call (seconds for an archive with thousands of entries); anything else still goes through it. +static QDateTime listTimeFromString(const QString &text) +{ + if (text.size() == 19 && text.at(4) == QLatin1Char('-') && text.at(7) == QLatin1Char('-') + && text.at(10) == QLatin1Char(' ') && text.at(13) == QLatin1Char(':') && text.at(16) == QLatin1Char(':')) { + bool ok[6] = {}; + const QDate date(text.mid(0, 4).toInt(&ok[0]), text.mid(5, 2).toInt(&ok[1]), text.mid(8, 2).toInt(&ok[2])); + const QTime time(text.mid(11, 2).toInt(&ok[3]), text.mid(14, 2).toInt(&ok[4]), text.mid(17, 2).toInt(&ok[5])); + if (ok[0] && ok[1] && ok[2] && ok[3] && ok[4] && ok[5] && date.isValid() && time.isValid()) { + return QDateTime(date, time); + } + } + return QDateTime::fromString(text, QStringLiteral("yyyy-MM-dd hh:mm:ss")); +} + bool CliRarPlugin::readListLine(const QString &line) { ArchiveData &stArchiveData = DataManager::get_instance().archiveData(); - QRegularExpression rxVersionLine(QStringLiteral("^UNRAR (\\d+\\.\\d+)( beta \\d)? .*$")); - QRegularExpressionMatch matchVersion = rxVersionLine.match(line); + // Compiled once and only matched before the version line: building and JIT-compiling it for every + // line of the listing took about as long as the rest of the parsing + static const QRegularExpression rxVersionLine(QStringLiteral("^UNRAR (\\d+\\.\\d+)( beta \\d)? .*$")); switch (m_parseState) { case ParseStateHeader: case ParseStateTitle: - if (matchVersion.hasMatch()) { // unrar信息读取完,开始读压缩包相关信息 + if (rxVersionLine.match(line).hasMatch()) { // unrar信息读取完,开始读压缩包相关信息 m_parseState = ParseStateArchiveInformation; } break; @@ -246,8 +265,7 @@ bool CliRarPlugin::readListLine(const QString &line) } else if (parseLineLeft == QLatin1String("mtime")) { QString time = line.left((line.length() - 10)); // 文件最后修改时间 - m_fileEntry.uLastModifiedTime = QDateTime::fromString(time.right(time.length() - 14), - QStringLiteral("yyyy-MM-dd hh:mm:ss")) + m_fileEntry.uLastModifiedTime = listTimeFromString(time.right(time.length() - 14)) #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) .toTime_t(); #else