0%

检测和诊断内存问题

性能是客户端永恒的主题,我们希望用户在使用APP时,能够获得极致的性能体验,关注内存占用也正是因为这一点。2:18

(温馨提示:本片内容来源自WWDC21:Detect and diagnose memory issues

想了解更多WWDC2021内容的小伙伴,可以阅读我以下文章,欢迎多多交流和指正

一文带你读完WWDC21核心(新)技术点

APP终极性能生存指南

iOS中的Hang是什么?该如何治理?

诊断APP的电源和性能回归

  • Faster application activations

    减少APP的内存占用,可以提高APP在后台存活的机会,从而让APP更快的被激活。

    设备的内存空间是有限的,监控APP的内存使用情况,能够防止系统为了回收内存而主动终止APP。

    这样即使APP在后台运行,也能够保存当前用户的使用状态,从而避免再次从内存加载导致的耗时。

  • Responsive experience

    更好的响应速度

    策略性的将APP的内容加载到内存里,能够避免在用户使用APP时,因系统回收内存增加的等待耗时。

  • Complex features

    让用户体验更丰富的功能

    像加载视频、展示动画,这些复杂的功能往往需要占用更多的内存。因此有策略的使用内存,可以避免在用户使用APP的复杂功能时,被系统因内存占用问题而终止。

  • Wider device compatibility

    更好的设备兼容性

    让内存空间不太充足的老设备也能更好的使用APP的功能

内存占用结构

2:26

  • Clean Memory

  • Dirty Memory

    Clean Memory被分配,并写入内容后成为”脏内存“,脏内存包括:

    • 所有的堆分配(malloc)
    • 解码图像缓冲区
    • Frameworks
  • Compressed Memory

    压缩内存特指脏页(Dirty Pages)中暂未被访问的部分(Unaccessed pages),会在访问后解压,成为脏内存。

    (下文会详细介绍脏页(Dirty Pages)的概念)

    注:iOS中没有内存交换(Memory Swap)的概念,你可能在使用Instrument工具时,见到过Swapped字段,实际上所指的是Compressed Memory

内存占用 = Dirty Memory + Compressed Memory

内存画像工具

3:52

  • XCTest

    通过单元测试和U测试中的XCTest来检测开发环境的性能指标

    • XCTest可以测量以下性能指标:

      • 内存使用情况
      • CPU使用
      • 磁盘写入情况
      • 卡顿率
      • 执行时间(完成某一任务所花的全部时间)
      • APP启动时间
    • 通过XCTest检测内存使用情况

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      func testSaveMeal() {
      let app = XCUIApplication()
      let options = XCTMeasuireOptions()
      options.invocationOptions = [.manullyStart]
      measure(metrics: [XCTMemoryMetric(application: app)],
      options:options) {
      app.launch()
      startMeasureing()
      app.cells.firstMatch.buttons["Save meal"].firstMatch.tap()

      let savedButton = app.cells.firstMatch.buttons["Saved"].firstMatch
      XCTAssertTure(savedButton.waitForExistence(timeout: 30))
      }
      }
    • XCTest在Xcode 13中新增的两项收集性能情况的诊断产物

      开启:enablePerformanceTestsDiagnostics YES

      1
      2
      3
      4
      5
      xcodebuild test
      -project MealPannerApp.xcodeproj
      -scheme PerformanceTests
      -destination platform=iOS,name="iPhone"
      -enablePerformanceTestsDiagnostics YES
      • Ktrace files

        Ktrace file能够打开,并展示以下分析报告:

        • 当卡顿发生时的渲染通道的状态
        • 因主线程阻塞,导致的Hang(APP无法响应用户的输入或者行为超过250ms,即记作一个hang)
      • Memory graphs

        Memory graph展示APP进程的地址空间的快照

        我们可以生成任务开始(pre_XXX.memgraph)和结束(post_XXX.memgraph)两个时机的内存快照,从而查看这一阶段内存占用的变化。

        7:19

  • MetricKit & Xcode Organizer

    检测线上环境的内存指标

内存问题

  • 内存泄漏(Leaks)

    最常见内存泄漏的原因的是循环引用

    11:18

    • 通过命令行查看memgraph文件

      12:28

  • 堆大小问题(Heap size issues)

    堆是进程地址空间中储存动态分配的对象的段(section),有策略性的加载和释放内存,可以避免内存峰值过高引发OOM。

    • 堆分配回归(Heap allocation regressions)
    • 碎片化(Fragmentation)

堆分配回归

  • 使用vmmap -summary对memgraph文件进行分析

15:31

  • 我们主要关心Dirty SizeSwapped Size两列数据

    16:09

  • 对任务开始(pre_XXX.memgraph)和结束(post_XXX.memgraph)两个时机的内存快照进行对比

    16:42

  • 可以在下方看到按类聚合的,各类对象占用内存的大小

    17:15

    从图中可以看到,non-object类型的数据,占用内存约13M。在Swift中,non-object通常代表原始分配字节(raw malloced bytes)

  • 打印memgraph文件中,类型为non-object且占用内存超过500k的对象

    17:36

  • 打印对象的引用树

    18:00

  • 可以通过malloc_history -fullStacks打印对象的分配调用栈

    19:42

  • 当不确定是哪个对象有问题时,可以通过leaks --referenceTree,自顶向下打印进程的所有内存中最可疑的对象

    18:40

    • 可以通过--groupByType参数,按type聚合简化打印结果。

    19:08

碎片化

先介绍两个概念:页和”脏页“

  • 页(Pages):是最小的独立的内存单元
  • 一旦页中写入任何数据,都会使整页变成”脏页“

21:37

当内存准备写入新的数据时,系统会优先尝试使用脏页中的空闲内存,而当即将分配的内存过大时,即使空闲内存的总大小足够,但其并不是一段连续的内存空间,仍会开辟一个新的脏页去写入这些数据。

这些无法被使用的空闲内存就是”碎片化内存“

再例如以下这种情况:我们分配的对象总共使用了4个页,但每个页的占用空间只占50%。

23:19

当我们释放这些对象后,这些4个脏页的内存空间仍有50%的碎片化内存。

最理想化的状态应该将所有的分配的对象放在两页,如下图:

23:42

这样一旦释放这些对象时,仅会留有两个脏页,并且不存在碎片化内存。

23:52

  • 我们的目标:

    • 让分配的对象尽可能连续且拥有同样的生命周期
    • 碎片化率保持在25%以下
    • 使用自动释放池
    • 特别注意长时间运行的进程
  • vmman -summary xx.memgraph查看碎片化内存情况

    25:00

    • DefaultMallocZone

      平时开发者只需要关注这部分的内存空间,因为这是我们进行堆分配默认结束的地方

    • MallocStackLoggingLiteZone

      这个空间是所有堆分配结束的地方

  • 使用Instruments工具中的Allocations track查看内存情况

    26:41

    其中的Destroyed和Persisted分别对应上面所描述的内存释放后的空闲内存(free memory)和仍未释放的内存(remaining objects)

    26:42

回顾下我们的总体排查流程:

27:51

更多WWDC有关性能的Session

iOS memory deep dive WWDC18

Getting started with Instruments WWDC19

Understand and eliminate hangs from your apps WWDC21