聊一聊MIUI最近更新的原子内存

是黑科技还是噱头?

Posted by YSY on August 23, 2021

前言

比起最近雷布斯的个人演讲,其实我更好奇MIUI 12.5增强版(超级Bug-list修复版)推出的那四个性能优化项目:

  • 焦点计算 - 处理器智能调度机制

  • 原子内存 - 精细化内存管理机制

  • 液态存储 - 文件存储管理机制

  • 智能均衡 - 对旗舰硬件性能的智能调配

作为技术爱好者,这个“原子内存”还真是提起了我的兴趣,因为其他三项还算比较好理解。毕竟内存管理这件事,是操作系统领域几乎永恒的课题。

官方解释

我们先来了解下MIUI官方网站是如何介绍原子内存的。官网的海报动效还是阐释得比较清晰明了(辛苦设计师小姐姐,不知道又加了多少班)。

最开始是几个独立的应用各自占有一定的内存,看看这绿,这不就是微信绿。看看这蓝,支付宝?看看这橙,这啥,淘宝吧估计是,肯定不是小米自己。设计姐姐在疯狂暗示。

然后,第二步是拆分应用内存,结束不重要的任务,刚才还是整块的内存占用现在在逻辑上被划分为了大小不等(即不同功能占用内存大小不同)的几块。这一步其实就是核心了,如何来划分呢?按什么维度划分呢?有没有可能划分失误反而影响用户体验呢?我们后面来聊。

第三步,根据场景,进一步精细压缩。这一步也很关键,不仅能结束优先级较低的任务,还能对剩余的任务进行内存占用的压缩。听起来有点玄乎,其实问题还是和第二步类似,如何对内存进行压缩?系统如何知道要压缩哪些内容呢?

带着上面这些问题,我们来简单聊一聊MIUI可能会如何实现这个原子内存,也即是说,以下内容均是基于个人经验提出的一些猜想,描述尽可能不那么硬核。

原理猜想

我们知道,现代操作系统基本都有进程(process)和线程(thread)的概念,用书本上的话说就是:

进程是资源分配的最小单位,线程是CPU调度的最小单位。

那么二者的关系,简单概括便是:一个运行中的应用程序就可以包含多个进程,一个进程又可以包含多个线程。

拿微信举例来说,主功能聊天是一个进程,内置浏览器又是另一个进程,进程之间可以进行通信,但又可以互不影响,比如浏览器崩溃了,不影响你聊天,这也是多进程的好处之一。然后,在聊天进程内部,你下载表情包时会开启另一个线程来完成任务,而不会影响你的UI渲染线程,保证了交互的流畅性,这就是多线程的好处。

大致了解这些基本概念后,我们就可以继续思考MIUI的原子内存了。

Android系统基于Linux,当然也有进程的概念。我们平时俗称的“杀进程”,一般就是指在最近任务中划掉相应的应用来终止其运行,在你划掉的那一瞬间,系统会杀掉此应用相关联的所有进程。而我们俗称的“被杀后台”,就是应用在置于后台且不可见的时候,被系统为了节约内存等资源而杀掉,这对用户而言是一种被动的杀进程,某种程度上比较影响使用体验。

在Android的官方文档中关于内存分配有这么一段话:

Android 平台在运行时不会浪费可用的内存。它会一直尝试利用所有可用内存。例如,系统会在应用关闭后将其保留在内存中,以便用户快速切回到这些应用。因此,通常情况下,Android 设备在运行时几乎没有可用的内存。要在重要系统进程和许多用户应用之间正确分配内存,内存管理至关重要。

其实这个解释和Apple官方之前建议用户不要频繁地手动杀进程是一个道理。因为应用启动,进程重新创建的开销是很大的。此外,接触过Android开发的同学都知道,系统在内存不足的时候也会尽可能地通知各应用,给了你“体面”处理自己的机会。

然而,很多时候国产手机厂商也是不得已而为之,因Android应用可以在后台运行的特性,不管是各类巨无霸应用,还是小而美应用,都在努力让自己在后台保活而不遵守开发规范,生怕用户离它们而去。但这样争奇斗艳的后果就是用户设备续航能力急剧下降,挨骂的往往就是手机系统厂商了。

“原子内存”想解决的便是这个问题,既能让更多的应用不被系统完全杀掉,也能减少系统资源的占用,在功耗和功能之间取得一个相对平衡。

拆分内存

其实在Android应用层面的开发当中,中小型应用的开发者日常很少会接触到进程的概念,更多的是Android自己抽象的一套开发框架。在其设计架构中,谷歌给开发者和用户提供了“四大组件”:

  • Activity - 可简单理解成我们打开的每一个活动页面,用于处理用户交互的逻辑

  • Service - 服务,和Activity的逻辑结构类似,只不过没有界面,比如用于播放音乐、下载文件等

  • BroadcastReceiver - 可理解为,Android系统就是一个巨大的世界频道,每个应用都可以用小喇叭广播消息,当然也需要接收消息

  • ContentProvider - 数据存储和提供的组件,内含标准接口,不同应用之间可通过此来共享内容

感兴趣的同学可以去安装一个叫LibChecker的应用,可以查看已安装应用都有哪些以上组件,往往一个应用会包含很多个各种各样的组件,并常以功能的维度来划分。

这些组件是Android开发的基础,对于任意一个组件,它都可以独占一个进程(比如我们上面提到的内置浏览器WebView),也可以和其他组件共享同一个进程,大多数情况都是后者。组件的本质也就是系统创建的一个内存对象,同时,每个组件都有自己的生命周期,在应用进程不被杀掉之前,可以提前结束(比如你打开登录页面完成登录,这个Activity的使命就完成了,可以被系统回收资源)。

所以在我第一时间看到MIUI原子内存的宣传时,就想到了对四大组件的精细化查杀。拆分内存这一行为可能实质上就是把应用内的各个组件进行分类并按优先级排序,销毁低优先级的组件,保证用户可见和正在使用的组件活着就行了,如此就能节省很多内存。当用户需要再次使用某组件时,重新创建启动便是。

又拿微信来举例,当你扫码支付后,又回到聊天界面,聊了几句再把微信置于后台,这时候你去干别的事情了,系统是不是可以把刚才支付相关的资源都回收掉呢?

当然,如果销毁和创建的行为过于频繁,也会消耗不少的系统资源,所以我猜测除了设计组件优先级等基本策略,还会有一些机器学习的玩法在里面,更精准地识别哪些可以被销毁。在用户使用过程中体验会越来越好,这是一个双向正反馈的过程。

最近在MIUI的官方论坛也看到一些解释提到“进程级”查杀,其实这个就更好理解了,因为本身很多业务相关性强的组件就在同一个进程内,一个大型应用往往有很多进程在同时运行,那么系统也可以杀掉某些暂时用不到的进程(相当于还能一次性销毁多个组件),而非粗暴地杀掉整个应用导致用户回来时一切都要重来。

精细压缩

说到压缩,我们可以从最基本的一些解释中了解原理:

它就是移除多余的空白字符,插入单个的重复字符指出一个字符串中重复的字符,以及将小型的位串用频繁使用的字符替代。

压缩其实是一个很朴素的概念,当你知道下一秒的画面跟这一秒一致时,是不是可以少存储几帧数据。在内存上,也是类似的方法,它是真正能够节约物理空间的。

在MIUI的海报中,有个非常关键的前提就是:根据场景。也就是说,并不是无脑地对所有内存占用进行压缩,而是根据用户使用的场景,来判断哪些进程或者组件所占用的内存可以压缩,哪些最好不要压缩,一切以用户体验为前提。这个压缩的策略,和上述的拆分内存类似,可能也会有机器学习的施展空间,当然,用黑白名单来进行简单粗暴的实现也不是不可以。

精细压缩这一步,从Android系统的体系结构来讲,总体可以分为两方面,一是交换压缩,二是垃圾回收

交换压缩

先看看官方介绍:

Android 设备包含三种不同类型的内存:RAM、zRAM 和存储器。

  • RAM 是最快的内存类型,但其大小通常有限。高端设备通常具有最大的 RAM 容量。

  • zRAM 是用于交换空间的 RAM 分区。所有数据在放入 zRAM 时都会进行压缩,然后在从 zRAM 向外复制时进行解压缩。这部分 RAM 会随着页面进出 zRAM 而增大或缩小。设备制造商可以设置 zRAM 大小上限。

  • 存储器中包含所有持久性数据(例如文件系统等),以及为所有应用、库和平台添加的对象代码。存储器比另外两种内存的容量大得多。在 Android 上,存储器不像在其他 Linux 实现上那样用于交换空间(swap),因为频繁写入会导致这种内存出现损坏,并缩短存储媒介的使用寿命

我们可以看出,zRAM是内存压缩的关键所在,相当于在内存上划分出一小块区域用于非活跃内存占用的压缩存储。这个“z”,大概就是zip的意思吧。所以,在内存压缩的底层实现上,原生Android已经搞定了,MIUI不需要自行实现这些内存管理机制,最多是在此基础上根据用户使用场景进行算法优化或者动态调整配置

这里特别要注意的是,官方文档提到:在 Android 上,存储器不像在其他 Linux 实现上那样用于交换空间。这一点也否认了最近网上关于MIUI原子内存的讨论中“就是Linux的swap机制而已”之类的看法。

我个人认为,Linux的swap机制不会特别适用于移动设备,因为移动设备的存储器基本上都是类似SSD的闪存,用作交换空间来存取内存数据是非常减寿的,对手机来说,闪存寿命下降就意味着卡顿。即便是现在的电脑,也不会设置那么大的swap空间甚至不设置了,因为很多电脑内存本身就很大。

垃圾回收

我们每一个应用都运行在一个独立的Android虚拟机之上,所以应用的每一个进程也可叫做虚拟机进程。Android虚拟机可以粗浅地看作Java虚拟机(JVM)的魔改版本,它不能运行原生Java程序。垃圾回收(GC)是JVM内存管理的核心机制之一。这里就不赘述了,简单来说就是系统对内存单元的整理和清除。

垃圾回收和刚才的交换压缩一样,它们管理内存的粒度都是“内存页(page)”,而不是进程和Android组件了,对于上层的应用来说,是没有什么感知的,系统底层在默默地自我调节。

MIUI原子内存在垃圾回收层面应该不会去做什么改动,因为GC机制比较底层,而且也相对成熟稳定,MIUI做的事情更多是用户层面的,业务层面的,属于GC的上层调用者。

精细压缩可能会在何时触发、如何更有效地触发垃圾回收上面下功夫

后话

做了这么多猜测,其实也没有特别深入的技术细节讨论,但我在查阅研究过程中,还是能感受到MIUI在系统层面的努力,而不仅仅限于做“UI”。毕竟这么花里胡哨的功能命名都唱出来了,怎么也得有点干货才行吧。

此外,写完了我才想到忘了提及以前的Android玩机工具写轮眼,它的主要功能就是由用户自行决定销毁哪些组件,MIUI原子内存或许也从中借鉴了一些思路。

还有一点收获就是,现在我们可以在很多移动设备用户场景遇到AI技术的落地或者是落地的可能性,借此机会,有空的话可以再跟大家聊聊端侧AI

13号当晚我也顺利升级到了这个MIUI 12.5增强版,感受了一下,同时开10个常用App,后台被杀的情况确实有所改善,而且剩余内存也比以前多了。对普通用户来说这就是简单真实的感受,有效果,说明有点东西。

参考

进程和应用生命周期 | Android Developers

进程间的内存分配 | Android Developers

如何评价 8 月 10 日小米正式推出的 MIUI 12.5 增强版?

MIUI 最近那个原子内存是什么原理? - V2EX