前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >QT进程启动慢问题探索

QT进程启动慢问题探索

原创
作者头像
lealc
修改2024-03-20 21:08:54
3980
修改2024-03-20 21:08:54

背景

电脑管家远程功能是使用qt编写的进程,启动耗时过长,导致用户体验较差

qt版本:5.15.2

模块签名时间:2020年11月6日

定位

利用微软官方提供的WPA来进行分析,录制etl打开

Graph Explorer -> Computation -> CPU Usage(Sampled)

这里需要注意区分:

在ETL(Event Tracing for Windows)分析中,CPU Usage(CPU使用率)是用于分析系统CPU性能的一种事件跟踪功能。通过分析CPU Usage事件,可以了解系统中各个进程和线程的CPU使用情况,以及系统整体的CPU负载情况。在ETL中,CPU Usage事件有以下几种类型:

CPU Usage:表示系统整体的CPU使用率。这种事件通常是由系统定时器触发,定期记录系统CPU使用情况。CPU Usage事件包含了系统CPU使用率的总体情况,例如CPU占用率、空闲率、中断率、DPC率等。

CPU Sampling:表示对进程或线程的CPU使用率进行采样。这种事件通常是由性能计数器或其他工具触发,定期对进程或线程的CPU使用率进行采样。CPU Sampling事件包含了进程或线程的CPU使用率、调用栈信息等。

CPU Time:表示进程或线程的CPU使用时间。这种事件通常是由进程或线程自身触发,记录进程或线程的CPU使用时间。CPU Time事件包含了进程或线程的CPU使用时间、调用栈信息等。

在分析CPU Usage事件时,需要注意区分不同类型的事件,以便进行针对性的分析和优化。例如,如果系统整体的CPU使用率过高,可以分析CPU Usage事件,找出占用CPU的进程或线程;如果某个进程或线程的CPU使用率过高,可以分析CPU Sampling或CPU Time事件,找出具体的CPU使用情况和性能瓶颈。

远程
远程
远程2
远程2
远程3
远程3

1、进程启动在第4.862秒

2、qwindows.dll!qt_getCanonicalFontNames触发了gdi32full.dll!EnumFontFamiliesExW从5.068s执行到5.824s,约莫耗时0.89s也就是756ms

分析

初步怀疑是qt组件出现bug,导致EnumFontFamiliesExW调用过多,尝试进一步定位

查阅文档知道qt程序会在初次使用字体的组件(例如text)渲染之前,枚举本地的字体库并尝试缓存所有的回退字体列表。

针对qt_getCanonicalFontNames查询源码,参考5.15.2的官方源码

代码语言:C++
复制
// qtbase\src\platformsupport\fontdatabases\windows\qwindowsfontdatabase.cpp

void QWindowsFontDatabase::populateFamily(const QString &familyName)
{
    // ***
    EnumFontFamiliesEx(dummy, &lf, storeFont, reinterpret_cast<intptr_t>(&sfp), 0);
    // ***
}

static int QT_WIN_CALLBACK storeFont(const LOGFONT *logFont, const TEXTMETRIC *textmetric,
                                     DWORD type, LPARAM lparam)
{
    // ***
    addFontToDatabase(familyName, styleName, *logFont, textmetric, signature, type, sfp);
    // keep on enumerating
    return 1;
}

static bool addFontToDatabase(QString familyName,
                              QString styleName,
                              const LOGFONT &logFont,
                              const TEXTMETRIC *textmetric,
                              const FONTSIGNATURE *signature,
                              int type,
                              StoreFontPayload *sfp)
{
    // 
    QFontNames canonicalNames = qt_getCanonicalFontNames(logFont);
    // 
}
代码语言:C++
复制
// qtbase\src\gui\text\qfontdatabase.cpp


void QtFontFamily::ensurePopulated()
{
    if (populated)
        return;
    QGuiApplicationPrivate::platformIntegration()->fontDatabase()->populateFamily(name);
    Q_ASSERT_X(populated, Q_FUNC_INFO, qPrintable(name));
}
代码语言:C++
复制
// qtbase\src\gui\text\qfontdatabase.cpp


/*!
    Returns a list of alternative fonts for the specified \a family and
    \a style and \a script using the \a styleHint given.
    Default implementation returns a list of fonts for which \a style and \a script support
    has been reported during the font database population.
*/
QStringList QPlatformFontDatabase::fallbacksForFamily(const QString &family, QFont::Style style, QFont::StyleHint styleHint, QChar::Script script) const
{
    // *** 
    for (int i = 0; i < db->count; ++i) {
        QtFontFamily *f = db->families[i];
        f->ensurePopulated();
        // ****
    }
    return preferredFallbacks + otherFallbacks;
}

针对fallbacksForFamily进一步查阅qt的bug发现:qtbug-71737

堆栈
堆栈

堆栈原因与我们出现的逻辑类似,这是摘取的简介

当从 Qt 4.8.7 切换到 Qt 5.9.7 时,我们注意到 Windows 10 上的应用程序启动存在明显的延迟。根据安装的字体数量,即使在快速工作站上,延迟也在大约 500 毫秒到 1 秒之间。我们可以将问题范围缩小到由 QPlatformFontDatabase::fallbacksForFamily 创建的系列回退缓存,这会导致每个字体系列大约有 500 到 1000 个回退列表。首次创建此大型回退字体列表需要花费大量时间。可以使用测试程序重现该问题:main.cpp。在第一个 paintEvent 中,将创建字体系列回退缓存列表。这会导致在 Windows 10 上显示空白按钮和按钮文本之间出现明显的延迟。

至此,大致上可以确定原因,但是如何修复呢,参考了qt的codereview可以看到此bug已经进行了修复

codereviewqt

codereviewqt
codereviewqt

对于5.15.2版本源码可以看到此修复MR并没有在里面,故可以确定5.15.2版本的qt仍存在此问题

代码语言:C++
复制
// 5.15.2源码:qtbase\src\gui\text\qfontengine.cpp
void QFontEngineMulti::ensureEngineAt(int at)
{
    if (!m_fallbackFamiliesQueried)
        ensureFallbackFamiliesQueried();
    Q_ASSERT(at < m_engines.size());
    if (!m_engines.at(at)) {
        QFontEngine *engine = loadEngine(at);
        if (!engine)
            engine = new QFontEngineBox(fontDef.pixelSize);
        Q_ASSERT(engine && engine->type() != QFontEngine::Multi);
        engine->ref.ref();
        m_engines[at] = engine;
    }
}

解决问题

挑选5.15.2后面的版本,例如5.15.6

查看官方源码:源码

代码语言:C++
复制
// 5.15.6 \qtbase\src\gui\text\qfontengine.cpp
void QFontEngineMulti::ensureEngineAt(int at)
{
    if (!m_fallbackFamiliesQueried && at > 0)
        ensureFallbackFamiliesQueried();
    Q_ASSERT(at < m_engines.size());
    if (!m_engines.at(at)) {
        QFontEngine *engine = loadEngine(at);
        if (!engine)
            engine = new QFontEngineBox(fontDef.pixelSize);
        Q_ASSERT(engine && engine->type() != QFontEngine::Multi);
        engine->ref.ref();
        m_engines[at] = engine;
    }
}

了解到5.15.6已经解决此问题,下载此模块32位版本替换:下载链接

替换后重新录制etl,对比EnumFontFamiliesExW执行时间从5.870s执行到6.205s(耗时0.335s),说明模块替换有效

替换后效果
替换后效果

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 背景
  • 定位
  • 分析
  • 解决问题
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档