精确计算应用的冷启动耗时

在iOS项目中,冷启动时间是指从用户点击应用图标开始,到应用完全加载并呈现出第一个界面(可能需要网络请求必要的数据)所花费的时间。这里以 main 函数为界,分为两个时间段:

  • 从用户点击应用图标 ~ invoke main func
  • invoke main func ~ 首屏渲染完成

计算 main 函数调用之前的耗时

通过添加环境变量 DYLD_PRINT_STATISTICS,可以准确计算应用程序的 Pre-main Time(即在 main 函数之前的启动时间)。DYLD_PRINT_STATISTICS 环境变量由动态链接器 dyld 提供,用于输出启动过程中各种操作的时间统计信息。

使用 DYLD_PRINT_STATISTICS 计算 Pre-main Time

  1. 打开 Xcode 并选择你的项目
  2. 选择项目的 Scheme,点击 Edit Scheme…
  3. 在左侧选择 Run 选项卡,然后选择 Arguments 子选项卡。
  4. Environment Variables 部分,点击 + 按钮添加新的环境变量:
    • Name: DYLD_PRINT_STATISTICS
    • Value: 1
  5. 点击 Close 保存更改。

运行应用并查看输出

  1. 运行你的应用程序。在 Xcode 的调试控制台中,你会看到 dyld 输出的详细启动时间统计信息,包括以下几个部分:
    • Total pre-main time:从启动应用程序到调用 main 函数之间的总时间。
    • dylib loading time:加载动态库所花费的时间。
    • rebase/binding time:重定位和绑定符号的时间。
    • ObjC setup time:Objective-C 运行时环境的初始化时间。
    • initializer time:执行全局和静态变量的初始化时间。

解析输出示例

以下是调试控制台中可能看到的输出示例:

Total pre-main time: 200.21 milliseconds (100.0%)
         dylib loading time:  50.03 milliseconds (25.0%)
        rebase/binding time:  30.01 milliseconds (15.0%)
            ObjC setup time:  20.05 milliseconds (10.0%)
           initializer time: 100.12 milliseconds (50.0%)

解释输出

  • Total pre-main time:总的 Pre-main 时间,从应用程序启动到 main 函数开始执行的时间。这包括动态库加载、重定位和符号绑定、Objective-C 运行时初始化、以及全局和静态变量的初始化时间。
  • dylib loading time:加载动态库(dylibs)所花费的时间。
  • rebase/binding time:重定位(rebase)和符号绑定(binding)所花费的时间。
  • ObjC setup time:初始化 Objective-C 运行时环境所花费的时间。
  • initializer time:执行全局和静态变量初始化所花费的时间。

通过这些数据,可以了解应用启动过程中各个阶段的时间消耗,特别是 Pre-main 时间,这对于优化应用的启动性能非常有帮助。

优化建议

根据 Pre-main 时间的统计信息,可以针对性地进行优化,例如:

  1. 减少动态库数量:减少应用程序依赖的动态库数量,可以显著减少动态库加载时间。
  2. 优化符号绑定:通过减少符号绑定和重定位的数量,可以降低 rebase/binding 时间。
  3. 优化全局变量初始化:避免复杂和耗时的全局变量初始化,尽量推迟到实际使用时再初始化。
  4. 减少 Objective-C 类的数量:减少 Objective-C 类的数量和复杂性,可以降低 ObjC setup 时间。

通过这些优化措施,可以有效减少 Pre-main 时间,从而提升应用的启动速度。

小结

通过添加环境变量 DYLD_PRINT_STATISTICS,可以准确计算和分析应用的 Pre-main 时间。这为优化应用的启动性能提供了详细的参考数据,使开发者能够更有针对性地进行优化。


计算 main 函数到首屏渲染完成的耗时

main 函数到完全呈现出第一个界面的时间,特别是在第一个界面涉及网络请求的情况下,计算这一段时间可以通过以下几步来实现:

方法 1:使用 os_signpost API 记录时间

在应用的关键点添加 os_signpost 标记,并结合 Instruments 工具进行分析。

步骤 1:设置 os_signpost 标记

main 函数中和第一个界面呈现完成后添加 os_signpost 标记。

import os.signpost

let log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "Performance")

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
    var window: UIWindow?
    var signpostID = OSSignpostID(log: log)

    static func main() {
        os_signpost(.begin, log: log, name: "App Launch", signpostID: OSSignpostID(log: log))
        UIApplicationMain(CommandLine.argc, CommandLine.unsafeArgv, nil, NSStringFromClass(AppDelegate.self))
    }

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // 初始化代码...
        return true
    }
}

class YourViewController: UIViewController {
    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        
        // 假设这是进行网络请求并更新界面的方法
        fetchDataAndUpdateUI()
    }
    
    func fetchDataAndUpdateUI() {
        // 开始网络请求
        let log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "Performance")
        let signpostID = OSSignpostID(log: log)
        os_signpost(.begin, log: log, name: "Fetch Data", signpostID: signpostID)
        
        // 模拟网络请求
        DispatchQueue.global().async {
            sleep(2) // 模拟网络延迟
            DispatchQueue.main.async {
                // 完成网络请求并更新UI
                os_signpost(.end, log: log, name: "Fetch Data", signpostID: signpostID)
                os_signpost(.end, log: log, name: "App Launch", signpostID: OSSignpostID(log: log))
                // 更新UI代码...
            }
        }
    }
}
步骤 2:使用 Instruments 工具进行分析
  1. 打开 Xcode 并选择你的项目
  2. 选择 Product > Profile 或使用快捷键 Command + I 启动 Instruments。
  3. 在 Instruments 中选择 Signpost Instrument 模板并点击 Choose
  4. 在 Instruments 界面中,点击 Record 按钮,启动你的应用。
  5. 在左侧的活动记录(Activity Trace)中,可以看到 App LaunchFetch Data 的开始和结束时间。

通过这种方法,可以准确地测量从 main 函数开始到第一个界面完全呈现出来的时间,包括网络请求所花费的时间。

方法 2:手动记录时间戳

在关键点手动记录时间戳,并计算总耗时。

步骤 1:记录时间戳

AppDelegate 和视图控制器中记录时间戳。

import UIKit

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
    var window: UIWindow?
    static var launchStartTime: TimeInterval?
    static var firstViewControllerLoadTime: TimeInterval?
    
    static func main() {
        launchStartTime = Date().timeIntervalSince1970
        UIApplicationMain(CommandLine.argc, CommandLine.unsafeArgv, nil, NSStringFromClass(AppDelegate.self))
    }

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // 初始化代码...
        return true
    }
}

class YourViewController: UIViewController {
    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        
        // 假设这是进行网络请求并更新界面的方法
        fetchDataAndUpdateUI()
    }
    
    func fetchDataAndUpdateUI() {
        // 开始网络请求
        let startTime = Date().timeIntervalSince1970
        
        // 模拟网络请求
        DispatchQueue.global().async {
            sleep(2) // 模拟网络延迟
            DispatchQueue.main.async {
                // 完成网络请求并更新UI
                let endTime = Date().timeIntervalSince1970
                if let launchStartTime = AppDelegate.launchStartTime {
                    let totalLaunchTime = endTime - launchStartTime
                    print("Total launch time including network request: \(totalLaunchTime) seconds")
                }
                // 更新UI代码...
            }
        }
    }
}
步骤 2:运行并查看输出
  1. 运行你的应用程序
  2. 在调试控制台中查看总的启动时间,包括网络请求的时间。

小结

通过上述方法,可以准确计算从 main 函数到第一个界面完全呈现出来的时间,尤其是在涉及网络请求的情况下。使用 os_signpost API 结合 Instruments 工具进行分析,可以提供更详细和精确的性能测量数据。而手动记录时间戳的方法则相对简单,但同样能提供一个大致的时间估计。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/769423.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

深度学习简介-AI(三)

深度学习简介 深度学习简介深度学习例子深度学习训练优化1.随机初始化2.优化损失函数3.优化器选择4.选择/调整模型结构 深度学习常见概念隐含层/中间层随机初始化损失函数导数与梯度优化器Mini Batch/epoch 深度学习训练逻辑图 深度学习简介 深度学习例子 猜数字 A: 我现在心…

24年河南特岗教师招聘流程+报名流程

河南特岗教师报名流程如下 1.登录河南省特岗招聘网 登录河南省特岗招聘网注册账号和密码,账号可以是手机号或者身份证号,密码自己设置 2.注册登录账号 注册完账号重新登录账号,输入身份证号、手机号、密码、验证码 3.浏览考试须知 填写个人信…

Unity Shader技巧:实现带投影机效果,有效避免边缘拉伸问题

这个是原始的projector 投影组件,边缘会有拉伸 经过修改shader 后边缘就没有拉伸了 (实现代码在文章最后) 这个着色器通过检查每个像素的UV坐标是否在定义的边界内,来确定是否应用黑色边框。如果UV坐标处于边缘区域,那么像素颜色会被强制设为黑色,从而在投影图像周围形成一…

JWT入门

JWT与TOKEN JWT(JSON Web Token)是一种基于 JSON 格式的轻量级安全令牌,通常用于在网络应用间安全地传递信息。而“token”一词则是一个更广泛的术语,用来指代任何形式的令牌,用于在计算机系统中进行身份验证或授权。J…

云原生技术架构详解

云原生技术最全详解(图文全面总结) 容器技术 容器技术:是将应用程序、及其所有依赖项,打包到一个独立的、可移植的容器中。 如下图所示: 容器技术的实现,最典型的就是以Docker为代表的。 如下图所示: 主要解决: 1、…

【ROS中Cjson文件的作用】

在ROS (Robot Operating System) 中,.json 文件通常用于存储配置信息、数据序列化或者在某些情况下用于网络通信和数据交换。JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,易于人阅读和编写,同时也易于…

UE4_材质_使用彩色半透明阴影

学习笔记,不喜勿喷!侵权立删,祝愿大美临沂生活越来越好! 本教程将介绍如何配置虚幻引擎来投射彩色半透明阴影。 此功能在许多应用中都很有用,常见例子就是透过彩色玻璃窗的彩色光。 一、半透明阴影颜色 阴影在穿过半…

【Python机器学习】模型评估与改进——带交叉验证的网格搜索

虽然将数据划分为训练集、验证集、测试集的方法是可行的,也相对常用,但这种方法对数据的划分相当敏感,为了得到对泛化性能的更好估计,我们可以使用交叉验证来评估每种参数组合的性能,而不是仅将数据单次划分为训练集与…

模拟退火算法2—优缺点

模拟退火算法优点 1、以一定的概率接受恶化解 模拟退火算法(SA)在搜索策略上与传统的随机搜索方法不同,它不仅引入了适当的随机因素,而且还引入了物理系统退火过程的自然机理。这种自然机理的引入使模拟退火算法在迭代过程中不仅接受使目标函数变“好”的试探点,而且还能以一…

Hadoop权威指南-读书笔记-02-关于MapReduce

Hadoop权威指南-读书笔记 记录一下读这本书的时候觉得有意思或者重要的点~ 还是老样子~挑重点记录哈😁有兴趣的小伙伴可以去看看原著😊 第二章 关于MapReduce MapReduce是一种可用于数据处理的编程模型。 MapReduce程序本质上是并行运行的&#xff0c…

行业模板|DataEase旅游行业大屏模板推荐

DataEase开源数据可视化分析工具于2022年6月发布模板市场(https://templates-de.fit2cloud.com),并于2024年1月新增适用于DataEase v2版本的模板分类。模板市场旨在为DataEase用户提供专业、美观、拿来即用的大屏模板,方便用户根据…

网络安全--计算机网络安全概述

文章目录 网络信息系统安全的目标网络安全的分支举例P2DR模型信息安全模型访问控制的分类多级安全模型 网络信息系统安全的目标 保密性 保证用户信息的保密性,对于非公开的信息,用户无法访问并且无法进行非授权访问,举例子就是:防…

Andriod安装termux并换源

问题汇总 Error: The repository ‘https://mirrors.tuna.tsinghua.edu.cn/termux/termux-package-24 stable Release’ does not have a Release file. 更换源(这里使用的是清华大学源) 打开文件 nano $PREFIX/etc/apt/sources.list手动修改 deb htt…

Powershell 获取电脑保存的所有wifi密码

一. 知识点 netsh wlan show profiles 用于显示计算机上已保存的无线网络配置文件 Measure-Object 用于统计数量 [PSCustomObject]{ } 用于创建Powershell对象 [math]::Round 四舍五入 Write-Progress 显示进度条 二. 代码 只能获取中文Windows操作系统的wifi密码如果想获取…

ETCD概述--使用/特性/架构/原理

ETCD概述 ETCD是一个高度一致的分布式键值存储, 它提供了一种可靠的方式来存储需要由分布式系统或机器集群访问的数据(高可用, 强一致性)​全局的配置服务中心. 本文将介绍其特性、相关操作和常见的应用场景. 如果想了解更多, 请查阅我的技术博客: https://dingyuqi.com 特性 …

红酒与文学:探索文字背后的酒香

在文学的海洋中,红酒如同一股不同的香风,轻轻拂过书页,为文字的世界增添了一抹诱人的色彩。今天,就让我们一起踏上这段奇妙的旅程,探索红酒与文学之间的奇妙联系,感受文字背后的酒香,特别是以雷…

第11章 规划过程组(二)(11.8排列活动顺序)

第11章 规划过程组(二)11.8排列活动顺序,在第三版教材第388~389页;文字图片音频方式 第一个知识点:主要工具与技术(重要知识点) 1、紧前关系绘图法(PDM)或前导图法&#…

宝塔Linux面板配置环境 + 创建站点

一、安装 (1)进入宝塔官网 https://www.bt.cn/new/index.html (2)点击“ 立即免费安装 ”,选择 Centos安装脚本 (3)进入 ssh 输入以下命令安装宝塔 yum install -y wget && wget -O …

【Docker安装】OpenEuler系统下部署Docker环境

【Docker安装】OpenEuler系统下部署Docker环境 前言一、本次实践介绍1.1 本次实践规划1.2 本次实践简介二、检查本地环境2.1 检查操作系统版本2.2 检查内核版本2.3 检查yum仓库三、卸载Docker四、部署Docker环境4.1 配置yum仓库4.2 检查可用yum仓库4.3 安装Docker4.4 检查Docke…

Golang 依赖注入设计哲学|12.6K 的依赖注入库 wire

一、前言 线上项目往往依赖非常多的具备特定能力的资源,如:DB、MQ、各种中间件,以及随着项目业务的复杂化,单一项目内,业务模块也逐渐增多,如何高效、整洁管理各种资源十分重要。 本文从“术”层面&#…