路标:断点不足时

上一篇文章中,我们了解了单元测试不稳定的原因以及如何处理它。 现在,我们要考虑苹果公司用于调试和分析代码的新工具之一。 我们正在谈论WWDC 2018上提出的os_log日志记录框架,该框架已通过性能分析工具os_signpost进行了扩展。



在其中一个冲刺中,我们的任务是在客户端实现pdf文档的生成。 我们完成了任务,并向团队成功展示了结果。 但是我们想确保该决定的技术细微差别的有效性。 路标帮助了我们。 有了它,我们能够多次加速文档的显示。

要了解有关os_signpost应用程序技术的更多信息,请深入了解它可以在哪些方面为您提供帮助以及如何为您提供帮助。

深入探讨问题


用户电话上有许多应用程序,它们都使用公用的系统资源:CPU,RAM,网络,电池等。 如果您的应用程序执行了任务并且没有崩溃,则并不意味着它可以有效且正确地工作。 下面我们描述您可能遇到的情况。

欠佳的算法可能导致较长的CPU负载。

  • 在应用程序启动时,等待20秒后,系统将关闭该应用程序,用户甚至看不到第一个屏幕。 在这种情况下,系统将设置一个崩溃报告,其区别特征将是异常类型EXC_CRASH(SIGKILL),类型为0x8badf00d
  • 后台线程中的资源密集型进程可能会影响UI的响应能力,增加电池消耗并迫使应用程序终止系统(以防CPU长时间过热)。

RAM案例:

Apple网站上的电话规格未提供有关RAM的信息,但其他来源为电话型号提供了以下内存分配:
型式
4S
5
5C
5秒
6
6P
6S
6SP
内存,GB
0.5
1个
1个
1个
1个
1个
2
2

型式
东南
X
7
7P
8
8P
XS
XSM
Xr
内存,GB
2
3
2
3
2
3
4
4
3

当可用RAM太少时,iOS将开始寻找要释放的内存,同时向所有正在运行的应用程序发送内存警告。 此过程隐式影响设备的CPU和电池。 如果忽略内存警告,并且继续分配内存,则系统将强制终止应用程序进程。 对于用户来说,这看起来像崩溃,崩溃报告中没有回溯痕迹。

过度使用网络请求 。 这也导致电池寿命的减少。 请求重复和/或缺少不必要请求的取消还导致CPU使用效率低下。

不要忘记CoreLocation 。 我们请求用户位置的频率越高,准确度越高,设备的电池消耗就越多。 为了验证处理上述情况的正确性,我们建议使用os_signpost来分析应用程序过程,然后分析所获得的数据。

项目中的工具集成


在顶层,创建PDF的过程包括三个步骤:

  1. 通过网络接收数据;
  2. 文件形成;
  3. 在屏幕上显示-我们决定拆分并记录文档生成的各个阶段,从用户单击“生成”按钮开始,直到文档在屏幕上显示为止。

假设我们面临着分析异步网络请求的任务。 代码中的标记将如下所示:

import os.signpost let pointsOfInterestLog = OSLog(subsystem: "com.example.your-app", category: . pointsOfInterest) let networkLog = OSLog(subsystem: "com.example.your-app", category: "NetworkOperations") os_signpost(.event, log: pointsOfInterestLog, name: "Start work") os_signpost(.begin, log: networkLog, name: "Overall work") for element in elements { os_signpost(.begin, log: networkLog, name: "Element work") makeWork(for: element) os_signpost(.end, log: networkLog, name: "Element work") } os_signpost(.end, log: networkLog, name: "Overall work") 

使用路标的步骤如下:

  • 导入os.signpost框架。
  • 创建一个OSLog实例。 值得考虑的事件有几种类型:对于间隔事件(例如,网络请求),可以使用任意类别;对于同时发生的事件(例如,单击按钮),可以使用预定义的类别pointsOfInterest / OS_LOG_CATEGORY_POINTS_OF_INTEREST。
  • 对于间隔事件,请在调查阶段的开始和结束时使用.begin和.end类型调用os_signpost函数。 对于同时发生的事件,请使用.event类型。
  • 如果所研究的代码可以异步执行,则添加路标ID,这将使您可以将具有不同对象的同一类型操作的间隔分开。
  • (可选)您可以将其他数据(元数据)添加到调度的事件中。 例如,通过网络下载的图像大小或生成的PDF页面数。 这些信息将有助于了解在代码执行的调查阶段究竟发生了什么。

同样在obj-c上:

 @import os.signpost; os_log_t pointsOfInterestLog = os_log_create("com.example.your-app",   OS_LOG_CATEGORY_POINTS_OF_INTEREST); os_log_t networkLog = os_log_create("com.example.your-app",   "NetworkOperations"); os_signpost_id_t operationIdentifier = os_signpost_id_generate(networkLog); os_signpost_event_emit(pointsOfInterestLog, operationIdentifier, "Start work"); os_signpost_interval_begin(networkLog, operationIdentifier, "Overall work"); for element in elements { os_signpost_id_t elementIdentifier = os_signpost_id_make_with_pointer(networkLog, element); os_signpost_interval_begin(networkLog, elementIdentifier, "Element work"); [element makeWork]; os_signpost_interval_end(networkLog, elementIdentifier, "Element work"); } os_signpost_interval_end(networkLog, operationIdentifier, "Overall work"); 

要注意。 如果该项目应在12.0版之前的iOS上运行,则Xcode将提供将os_signpost调用包装在if #available结构中。 为了不使代码混乱,您可以将此逻辑放在单独的类中。

值得考虑的是,os_signpost需要静态字符串文字作为事件名称的参数。 要添加更严格的类型,可以创建带有事件类型的枚举,并在类实现中将它们映射到字符串文字。 将OSLog放在单独的类中将添加逻辑以针对发布方案禁用它(为此存在单独的OSLog命令)。

 import os.signpost let networkLog: OSLog if ProcessInfo.processInfo.environment.keys.contains("SIGNPOSTS_FOR_NETWORK") { networkLog = OSLog(subsystem: "com.example.your-app", category: "NetworkOperations" } else { networkLog = .disabled } 

您可以使用以下类型解码器将任何属性的值添加到事件标记中,以方便格式化:

值类型
自定义说明符
输出示例
time_t
%{time_t} d
2016-01-12 19:41:37
时间间隔
%{timeval}。* P
2016-01-12 19:41:37.774236
时间规格
%{timespec}。* P
2016-01-12 19:41:37.2382382823
埃尔诺
%{errno} d
断管
字节
%{iec-bytes} d
2.64 MiB
比特率
%{bitrate} d
123 kbps
比特率
%{iec-bitrate} d
118千bps
uuid_t
%{uuid_t}。* 16P
%{uuid_t}。* P
10742E39-0657-41F8-AB99-878C5EC2DCAA

现在,在对应用程序进行性能分析时,来自os_signpost的事件将以表格数据的形式发送到Instruments。 要切换到工具,请使用键盘快捷键Cmd + I,然后选择进行分析所需的工具。 要查看标记的数据,只需打开工具界面右侧的os_signpost和Point of Interest工具。



默认情况下,事件被分组并显示在表格中,在表格中计算事件的数量和统计信息。 此外,时间轴上有图形显示,可轻松将接收到的事件与其他工具中的结果进行比较。 还可以自定义统计数据的显示并编写专家系统-但该主题值得单独撰写。

使用范例


案号1。 PDFKit与WKWebView


通过使用os_signpost,我们看到对于小型文档(几页),最长的一步是最后一步-显示文档-而不是使用网络或图形。 这导致我们决定将WKWebView替换为PDFView ,从而将文档的显示从1.5秒加速到30毫秒。 在图形上,它看起来像这样:


Time Profiler中显示PDF文档(WKWebView)


Time Profiler中显示PDF文档(PDFView)

生成的数据可以在Xcode提供的其他工具中实现。 如分配工具所示,下载速度的提高是通过增加RAM的使用来实现的。

案号2。 低内存警告


PDF文档是异步生成的,其形成需要分配大量的内存。 在内存不足的情况下,我们决定增加停止创建文档的异步操作的功能。

如您所知,在使用NSOperationQueue时,cancelAllOperation方法将释放现有队列,但不会停止已经在运行的操作。 由此得出的结论是,在执行操作过程中,有必要定期确定其状况并停止工作。 因此,如果将资源设置为“已取消”状态,则可以释放资源。

下一步是异步操作,我们需要检查是否取消。 但同时,尚不清楚以何种频率进行此检查。 我们有两个选择-逐行检查和逐页检查。 os_signpost也提供了帮助。 事实证明,在逐行显示文档中表格的周期中添加了取消检查,我们将生成文档的时间(增加了150页)增加了2倍。 第二个选项在性能方面更为理想,实际上并没有增加创建文档所需的时间。 结果,当我们收到内存警告事件时,我们将以编程方式取消该操作并为用户显示错误屏幕。

为了确保确实释放了内存,我们还可以使用os_signpost。 这次通过在didRecieveMemoryWarning方法中添加有关事件开始的标记,并在错误屏幕的viewDidLoad中添加有关结束的标记。 顺便说一句,您可以在模拟器中模拟内存不足事件(shift + command + m)。

案号3。 更新约束


路标可能在布局过程中很有用。 为了创建约束,我们使用了砌体框架。 该框架的文档说,建议使用updateConstraints()方法创建构造。 但是Apple强烈建议您不要这样做,您可以使用路标标记进行验证。



根据Apple的文档,如果我们不能在发生更改的地方进行操作,则只能将updateConstraints用于更改约束。



在分析结果之后,我们得出结论,在我们的应用程序中,updateConstraints调用不是那么频繁-大约每次视图出现在屏幕上时。
尽管如此,为了避免潜在的性能缺陷,我们建议遵循Apple的建议。

结论


苹果在2018年为开发人员提供了独立扩展性能分析工具的机会。 当然,您可以使用其他调试工具:断点,输出到控制台,计时器,自定义探查器。 但是他们需要花费更多的时间来实施,或者并不总是对所发生的事情有完整的了解。

在下一篇文章中,我们将考虑通过编写我们自己的专家系统(Custom Instruments),更有效地使用从路标收到的信息。



有用的链接


本文由@victoriaqb撰写-iOS开发人员Victoria Kashlina。

Source: https://habr.com/ru/post/zh-CN443414/


All Articles