乐趣区

关于高德地图:高德地图驾车导航内存优化原理与实战

​背景

依据 Apple 官网 WWDC 的答复,缩小内存能够让用户体验到更快的启动速度,不会因为内存过大而导致 Crash,能够让 APP 存活的更久。

对于高德地图来说,依据线上数据的剖析,内存过高会导致导航过程中零碎强杀 OOM。尤其区别于其余 APP 的中央是,个别 APP 只须要关注前台内存过高的零碎强杀 FOOM,高德地图有不少用户应用后盾导航,所以也须要关注后盾的内存过高导致的零碎强杀 BOOM,且后盾强杀较前台强杀更为严重。为了晋升用户体验,内存治理火烧眉毛。

原理分析

OOM

OOM 是 Out of Memory 的缩写。在 iOS APP 中如果内存超了,零碎会把 APP 间接杀死,一种另类的 Crash,且无奈捕捉。发现 OOM 时,咱们能够从 设施 -> 隐衷 -> 剖析与改良 -> 剖析数据 中找到以 JetsamEvent 结尾的日志,日志外面记录了很多信息:手机设施信息、零碎版本、内存大小、CPU 工夫等。

Jetsam

Jetsam 是 iOS 零碎的一种资源管理机制。不同于 MacOS、Linux、Windows 等,iOS 中没有内存替换空间,所以在设施整体内存缓和时,零碎会将一些 优先级不高或者占用内存过大 的间接 Kill 掉。

通过 iOS 开源的 XNU 内核源码能够剖析到:

  • 每个过程在内核中都存在一个优先级列表,JetSam 在受到内存压力时会从优先级列表最低的过程开始尝试杀死,直到内存水位复原到失常水位。
  • Jetsam 是通过 get_task_phys_footprint 获取到 phys_footprint 的值,来决定要不要杀掉利用。

Jetsam 机制清理策略能够总结为以下几点:

  • 单个 APP 物理内存占用超过下限会被清理,不同的设施 内存水位线 不一样。
  • 整个设施物理内存占用受到压力时,优先清理后盾利用,再清理前台利用。
  • 优先清理内存占用高的利用,再内存占用低的利用。
  • 相比零碎利用,会优先清理用户利用。

Android 端为 Low Memory Killer:

  • 依据 APP 的优先级和应用总内存的多少,零碎会在设施内存吃紧状况下强杀利用。
  • 内存吃紧的判断取决于零碎 RSS(理论应用物理内存,蕴含共享库占用的全副内存)的大小。
  • 要害参数有 3 个:

1)oom_adj:在 Framework 层应用,代表过程的优先级,数值越高,优先级越低,越容易被杀死。

2)oom_adj threshold:在 Framework 层应用,代表 oom_adj 的内存阈值。Android Kernel 会定时检测以后残余内存是否低于这个阀值,若低于则杀死 oom_adj ≥该阈值对应的 oom_adj 中,数值最大的过程,直到残余内存复原至高于该阀值的状态。

3)oom_score_adj:在 Kernel 层应用,由 oom_adj 换算而来,是杀死过程时理论应用的参数。

数据分析

phys_footprint 获取 iOS 利用总的物理内存,具体能够参考官网阐明 iOS Memory Deep Dive.

std::optional<size_t> memoryFootprint()
{
    task_vm_info_data_t vmInfo;
    mach_msg_type_number_t count = TASK_VM_INFO_COUNT;
    kern_return_t result = task_info(mach_task_self(), TASK_VM_INFO, (task_info_t) &vmInfo, &count);
    if (result != KERN_SUCCESS)
        return std::nullopt;
    return static_cast<size_t>(vmInfo.phys_footprint);
}

Instruments-VM Tracker能够用来剖析具体内存分类,比方 Malloc 局部是堆内存,Webkit Malloc 局部是 JavaScriptCore 占用的内存等。须要留神的是每个分类的内存值 = Dirty Size + Swapped。

通过 Instruments VM Tracker 抓取导航中内存散布进行比照剖析。导航前台静置时,高德地图的总内存数值十分高,其中 IOKit、WebKit Malloc 和 Malloc 堆内存为内存占用大头。

在剖析过程中能够应用的工具很多,各有优缺点,须要配合应用,互相补救。咱们在剖析的过程中次要用到 Intruments VM Tracker、Allocations、Capture GPU Frame、MemGraph、dumpsys meminfo、Graphics API Debugger、Arm Mobile Studio、AJX 内存剖析工具、自研 Malloc 剖析工具等。

  • IOKit 内存为地图渲染显存局部。
  • WebKit Malloc 内存为 AJX JS 业务内存。
  • Malloc 堆内存,咱们通过 Hook Malloc 分配内存的 API,通过抓取堆栈剖析具体内存消费者。

治理优化

依据下面的数据分析,很容易做出从大头开始抓起的思路。咱们在治理过程中的大体思路:

  • 剖析数据:从内存大头开始,剖析各内存归属业务,以便业务进一步剖析优化。
  • 内存治理:优化技术计划缩小内存开销、高下端机性能分级和智能容灾(即内存告警时通过性能降级等策略开释内存)。

分而治之

据数据分析,高德地图三大内存耗费别离是地图渲染(Graphic 显存)、性能业务(JavaScriptCore)和通用业务(Malloc)。咱们也次要从这三个方面动手优化。

地图 Graphic 显存优化

Xcode 自带 Debug 工具 Capture GPU Frame,能够剖析出具体显存占用,显存次要分为纹理 Texture 局部和 Buffer 局部,通过具体的地址信息剖析具体耗费。Android 端相似剖析显存工具能够用 Google 的 Graphics API Debugger。

依据剖析,Texture 局部咱们通过 FBO 绘制形式调整、矢量路口大图背景优化、图标跨页面开释、文字纹理优化、低端机关闭全屏抗锯齿等缩小显存耗费。Buffer 局部通过开启低显存模式、敞开四叉树预加载、切后盾开释缓存资源等。

Webkit Malloc 优化

高德地图应用的是自研的动态化计划,依赖于 iOS 零碎提供的框架 JavaScriptCore,应用的业务内存耗费大多会被零碎归类到 WebKit Malloc,从零碎工具 Instruments 上的 VM Tracker 能够看出。此处有两个思路,一个是业务本身优化内存耗费,第二个是动态化引擎和框架优化内存耗费。

业务本身优化,动态化计划的 IDE 提供内存剖析工具能够清晰的输入具体业务内存耗费在什么中央,便于业务同学剖析是否正当。

动态化引擎和框架优化,咱们通过优化对系统库 JavaScriptCore 的应用形式,即多个 JSContextRef 上下文共享同一份 JSContextGroupRef 的形式。多个页面能够共享一份框架代码,从而缩小内存开销。

Malloc 堆内存优化

iOS 端堆内存调配基本上应用的 libmalloc 库,其中蕴含以下几个内存操作接口:

// c 调配办法
void    *malloc(size_t __size) __result_use_check __alloc_size(1);
void    *calloc(size_t __count, size_t __size) __result_use_check __alloc_size(1,2);
void     free(void *);
void    *realloc(void *__ptr, size_t __size) __result_use_check __alloc_size(2);
void    *valloc(size_t) __alloc_size(1);

// block 调配办法
// Create a heap based copy of a Block or simply add a reference to an existing one.
// This must be paired with Block_release to recover memory, even when running
// under Objective-C Garbage Collection.
BLOCK_EXPORT void *_Block_copy(const void *aBlock)
    __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_2);

通过 hook 内存操作 API 记录下内存调配的堆栈、大小,即可剖析内存应用状况。

同时源码中还存在一个全局钩子函数 malloc_logger,可输入 Malloc 过程中的日志,定义如下:

// We set malloc_logger to NULL to disable logging, if we encounter errors
// during file writing
typedef void(malloc_logger_t)(uint32_t type,
        uintptr_t arg1,
        uintptr_t arg2,
        uintptr_t arg3,
        uintptr_t result,
        uint32_t num_hot_frames_to_skip);
extern malloc_logger_t *malloc_logger;

iOS 堆内存剖析计划,可通过 hook malloc 系列 API,也能够设置 malloc_logger 的函数实现,即可记录下堆内存应用状况。

此计划有几个难点问题,每秒钟内存调配的量级大、内存有调配有开释须要高效查问和堆栈反解聚合。为此咱们设计了一套残缺的 Malloc 堆内存剖析计划,来满足疾速定位堆内存归属,以便散发到各自业务 Owner 剖析优化。

对立治理

随着业务的增长给高德地图这个超级 APP 带来了极大资源压力,因而咱们积淀了一套自适应资源管理框架,来满足不同业务场景在无限资源下可能做到性能和体验极致平衡。次要的设计思路是通过监测用户设施等级、零碎状态、以后业务场景以及用户行为,利用调度算法进行实时推算,对立治理协调 APP 以后资源状态调配,对用户以后不可见的内存等资源进行回收。

自适应资源管理框架 - 内存局部

能够依据不同的设施等级、业务场景、用户行为和零碎状态来治理资源。各业务都能够很容易的接入此框架,目前曾经利用到多个业务场景,均有不错的收益。

数据验收

通过三个版本的间断治理,前后台导航场景均有 50% 的收益,同时 Abort 率也有 10%~20% 的收益。整体收益算是比拟乐观,然而随之而来的挑战是咱们该如何守住成绩。

长线管控

所谓打江山容易守江山难,如果没有长线管控的计划,随着业务的版本迭代,不出三五个版本就会将先前的优化耗费。为此咱们构建了一套 APM 性能监控平台,在研发测试阶段发现并解决问题,不把问题带上线。

APM 性能监控平台

为了将 APP 的性能做到日常监控,咱们建设了一套线下「APM 性能监控平台」,平台可能反对惯例业务场景的性能监控,包含:内存、CPU、流量等,可能及时的发现问题并进行报警。再配合性能跟进流程,为客户端性能保障把好最初一关。

内存剖析工具

Xcode memory gauge:在 Xcode 的 Debug navigator 中,能够粗略查看内存占用的状况。

Instruments – Allocations:能够查看虚拟内存占用、堆信息、对象信息、调用栈信息、VM Regions 信息等。能够利用这个工具剖析内存,并针对地进行优化。

Instruments – Leaks:用于检测内存透露。

Instruments – VM Tracker:能够查看内存占用信息,查看各类型内存的占用状况,比方 dirty memory 的大小等等,能够辅助剖析内存过大、内存透露等起因。

Instruments – Virtual Memory Trace:有内存分页的具体信息,具体能够参考 WWDC 2016 – Syetem Trace in Depth。

Memory Resource Exceptions:从 Xcode 10 开始,内存占用过大时,调试器能捕捉到 EXC_RESOURCE RESOURCE_TYPE_MEMORY 异样,并断点在触发异样抛出的中央。

Xcode Memory Debugger:Xcode 中能够间接查看所有对象间的相互依赖关系,能够十分不便的查找循环援用的问题。同时,还能够将这些信息导出为 memgraph 文件。

memgraph + 命令行指令:联合上一步输入的 memgraph 文件,能够通过一些指令来剖析内存状况。vmmap 能够打印出过程信息,以及 VMRegions 的信息等,联合 grep 能够查看指定 VMRegion 的信息。leaks 可追踪堆中的对象,从而查看内存透露、堆栈信息等。heap 会打印出堆中所有信息,不便追踪内存占用较大的对象。malloc_history 能够查看 heap 指令失去的对象的堆栈信息,从而不便地发现问题。

总结:malloc_history ===> Creation;leaks ===> Reference;heap & vmmap ===> Size。

MetricKit:iOS 13 新推出的监控框架,用于收集和解决电池和性能指标。当用户应用 APP 的时候,iOS 会记录各项指标,而后发送到苹果服务端上,并主动生成相干的可视化报告。通过 Window -> Organizer -> Metrics 可查,包含电池、启动工夫、卡顿状况、内存状况、磁盘读写五局部。也能够 MetricKit 集成到工程里,将数据上传到本人的服务进行剖析。

MLeaksFinder:通过判断 UIViewController 被销毁后其子 view 是否也都被销毁,能够在不入侵代码的状况下检测内存透露。

Graphics API Debugger:Google 开源的一系列的 Graphics 调试工具,能够查看、微调、重播利用对图形驱动的 API 调用。

Arm Mobile Studio: 专业级 GPU 剖析工具。

退出移动版