6

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

 3 years ago
source link: https://my.oschina.net/amaptech/blog/4920788
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

​背景

根据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。

YbqUvyj.png!mobile

根据分析,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当前资源状态分配,对用户当前不可见的内存等资源进行回收。

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

可以根据不同的设备等级、业务场景、用户行为和系统状态来管理资源。各业务都可以很容易的接入此框架,目前已经应用到多个业务场景,均有不错的收益。

uyaABj6.png!mobile

数据验收

通过三个版本的连续治理,前后台导航场景均有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分析工具。

ryuMJfq.jpg!mobile


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK