背景
某次嘉宾直播重保项目中,直播中出现了声音卡顿、爆音问题,经过排查得出一个结论:嘉宾直播时手机处于充电状态,手机出现发热导致降频,发热降频导致系统采集线程调度出现问题,属于系统行为,影响到系统采集的输入输出,最终出现声音异常问题。
该结论乍一听似乎没什么说服力,很容易让人不认同这个结论,并且有一些质疑问题,比如:
(资料图)
问题一:如果是性能问题,为什么她说话感觉不卡,而是爆音的现象?问题二:通过对音视频采集的埋点等信息能更精确的定位吗?“发热降频导致的系统采集线程调度出问题,属于系统行为”是明确结论还是推断?怎么判定的?问题三:CPU 为什么异常变高?很多同学都知道 iOS 发热降频这个事情,但是可能不太清楚如何量化影响、如何系统化或者说量化去分析。
本文将基于这个典型案例,做一个系统性的分析,分享 iOS 发热降频的基本概念与处理经验,希望能够解决大家对 iOS 发热降频的一些疑惑,帮助大家在下次遇到类似问题时,能够知道如何分析问题、如何发现证据、如何解决问题。
基本概念
AURemoteIO 线程
AURemoteIO:IOThread 是系统创建专门用于采集和播放或者说是输入、输出的线程,有两个特点:
输入输出是串行的,所以相互之间会受到影响;线程回调时间是稳定的,且要求实时性非常高,如果存在阻塞,会导致输入输出都受到影响,出现声音异常。异常现象会根据阻塞程度不同而不同,轻微阻塞采集数据不连续,严重一些会明显感觉卡顿、爆音,极其严重的情况会直接导致采集线程关掉出无声数据,这一点解释了背景中提到的问题一。iOS 的线程调度
概述
由于 Mach 具有处理器集的抽象,所以从某个角度说,Mach 比 Linux 和 Windows 更擅长管理多核处理器。
上下文切换
暂停某个线程的执行,并且将其寄存器状态记录在某个预定义的内存位置中。当一个线程被抢占时,CPU 寄存器中会记住另一个线程保存的线程状态,从而恢复那个线程的执行。
一个线程在 CPU 上可以执行任意长的时间。CPU 寄存器中填满了线程的状态,这个执行过程一直在延续,直到发生下面某种情况:
线程终止线程自愿放弃外部中断打断了线程的执行,外部中断要求 CPU 保存线程状态并且立即执行中断处理代码优先级
每一个线程都被分配了优先级,优先级直接影响线程被调度的频率。每一个操作系统都提供了一个这种优先级的范围:Windows 有 32 个优先级,Linux 有 140 个优先级,Mach 有 128 个优先级。
内核线程的最低优先级为 80,比用户态线程的优先级要高。可以保证内核以及用户维护管理的线程能够抢占用户态的线程。
iPhone 性能与电池的关系
苹果系统在进入低电量模式时都会回收一部分系统资源,降低处理器主频来保证系统的稳定性,也就是我们俗称的降频。
在需要更极端的性能管理的情况下,用户可能会发现以下影响:
App 启动时间变长滚动时帧速率降低背光灯变暗(可在“控制中心”手动调整)扬声器音量降低幅度高达 -3dB部分 App 的帧速率逐渐降低在最极端的情况下,相机闪光灯会被停用(会显示在相机用户界面上)在后台刷新的 App 在启动时可能需要重新载入iOS11.1 以上系统都有降频策略;系统>=iOS13.1,默认开启降频功能,但用户可以手动选择关闭。iOS 系统常见的降频策略有:
如果主动设置了省电模式,系统会主动降频。如果硬件设备老化,如电池老化(设置->电池容量峰值<80%),也会出现降频。CPU 占用上升较快时,会加快降频。能效
苹果系统在发热严重时同样会降频甚至降核。
State/constant | Impact | Recommended action |
---|---|---|
Nominal /NSProcessInfoThermalStateNominal | The device"s temperature-related conditions (thermals) are at an acceptable level. There is no noticeable negative impact to the user. | No corrective action is necessary. As always, all apps should be proactive about optimizing energy use, in order to help ensure that thermals do not begin to rise. For example, discretionary background work should be assigned quality of service levels and scheduled for execution at optimal times using NSBackgroundActivityScheduler or an NSURLSession background session. |
Fair /NSProcessInfoThermalStateFair | Thermals are minimally elevated. On devices with fans, those fans may become active, audible, and distracting to the user. Energy usage is elevated, potentially reducing battery life. | Although no corrective action is required, reaching this level provides an opportunity for greater proactive action. An app can begin reducing CPU usage and enacting other energy-saving measures to help ensure that the thermal state doesn’t rise any further. |
Serious /NSProcessInfoThermalStateSerious | Thermals are highly elevated. Fans are active, running at maximum speed, audible, and distracting to the user. System performance may also be impacted as the system begins enacting countermeasures to reduce thermals to a more acceptable level. | Apps should begin to take corrective action to help the system reduce thermals and create a more optimal user experience. To achieve this, wherever possible, apps should:Reduce CPU usage.Reduce GPU usage.Reduce I/O.Reduce frame rates.Use lower-quality visual effects. |
Critical /NSProcessInfoThermalStateCritical | Thermals are significantly elevated. The device needs to cool down. | Apps can help the system by taking immediate corrective action to reduce usage of the CPU, GPU, and I/O to the absolute minimum level necessary in order to respond to user actions. Wherever possible, apps can stop using peripherals, such as the camera. |
某嘉宾直播中 iOS 发热降频案例分析
某次嘉宾直播重保项目中,发现直播中出现了声音卡顿爆音问题。
排查沟通中,获得反馈:
团队通过业务埋点发现,对应时刻(20:36),嘉宾的电池状态显示在充电(state=2),但不是低电量模式,CPU 占比 131%。
经验分析
从嘉宾侧反馈的信息中,可以抽出几个关键信息:
开播时没有出现问题,从 20:36 开始,说明:直播持续了一段时间后才出现;强杀 APP 重开也不行,说明:出现异常后,无法恢复,都说重启大法好,其实就是为了排除或者说是恢复命中的异常条件导致的问题,结果这次不好用了;存在充电和发热情况,且 CPU 占比达 131%;从前两个点可以分析出,大概率原因不在代码里。第三点采集到的 CPU 数据异常高,过往经验正常的值应该在 50 - 60 左右。由此推测是命中降频了。
那怎么拿出证据呢?
系统分析
状态上报
通过观测系统通知,获取电流状态和发热情况,上报埋点可以判断设备及系统情况,也可以响应通知做一些降级策略。
//注册电流状态通知[[NSNotificationCenter defaultCenter] addObserver:self selector: @selector(yourMethodName:) name: NSProcessInfoPowerStateDidChangeNotification object: nil];if ([[NSProcessInfo processInfo] isLowPowerModeEnabled]) { // Low Power Mode is enabled. Start reducing activity to conserve energy.} else { // Low Power Mode is not enabled.};
//注册发热情况通知[[NSNotificationCenter defaultCenter] addObserver:self selector: @selector(yourMethodName:) name: NSProcessInfoThermalStateDidChangeNotification object: nil];NSProcessInfoThermalState state = [[NSProcessInfo processInfo] thermalState];if (state == NSProcessInfoThermalStateFair) {// Thermals are fair. Consider taking proactive measures to prevent higher thermals.} else if (state == NSProcessInfoThermalStateSerious) {// Thermals are highly elevated. Help the system by taking corrective action.} else if (state == NSProcessInfoThermalStateCritical) {// Thermals are seriously elevated. Help the system by taking immediate corrective action.} else {// Thermals are okay. Go about your business.};
此外,在介绍 AURemoteIO 线程时讲到过,线程回调时间是稳定且要求实时性非常高,如果存在阻塞,会导致输入输出都受到影响。出现声音异常,我们利用内部 SDK 工具针对 io 线程的耗时做了上报,通过耗时的异常增加可以判断出线程时间片被抢占后的挂起现象。
下图中,三列数据,左侧是事件时间点,中间是输入,右侧是输出。可以看到,20:34:07-20:34:37 这段时间中,输入是正常状态,维持在 1.42-1.64 区间。从 20:34:47 开始的 1 秒时间内,输入出现了大幅提升,最低为 3.60,最高甚至达到了 50.89 ,超出了正常数值的很多倍。 20:34:47 - 20:35:47 这个时间点,也和嘉宾侧反馈的问题出现时间相近。
工具分析
降频情况:
正常情况:
从 System load 看降频下的黄色占了绝大部分,可以从中判断出,降频后绝大多数时刻线程负载都超出了 CPU 性能,接下来我们再看看线程调度情况。
降频情况:
正常情况:
从 audioMixerThread 线程看,发热情况下黄色被抢占的时间明显增多,在资源紧缺(发热降频、低端机、耗时任务数较多)的时候,低优先级线程会被高优先级的线程抢占的比较严重,比如,系统开辟的高优先级线程(UI 线程、coreanimation、AppleBCMWLANBusInterfacePCIe 等等)。当然, Mach 系统并不会让低优先级的线程一直处于饥饿状态,会根据系统实际情况去调整优先级,audioMixerThread 在后面从 27 的优先级调高到了 31。
正常情况:
降频情况:
另外,虽然 AURemoteIO 有较高的线程优先级,但依然有可能被更高优先级的线程抢占,且在资源紧缺的情况下,线程的执行时间明显减少至正常情况的三分之一。正常情况:1600us 左右 降频情况:500us 左右。(注:此次截图是基于 demo 模拟测试,如果在抖音上测试会更严重)
如何避免 iOS 发热降频导致直播声音卡顿、爆音问题
重保增加约束
项目重保时,约束用户手机至少保持 80% 以上的电量,最好是充满。如果无法保证电量充足的条件,则需要指导用户手动设定低电流时禁止开启低电量模式(节能模式),建议用户不要一边直播一边充电。部分重保场景,推荐主播用水冷,降低手机发热出现几率;采用性能更好的 iPhone 机型,这里的性能更好并不完全等价于更高端,而是跟系统版本和特定机型都有关系,比如 14.5.1 版本系统就被吐槽降频严重,以及过往经验显示 iPhone X 发热后降频非常严重。优化降低能耗
在正常情况下,我们可以进一步优化能耗,从而降低发热的可能性,或让发热来的更慢一些。这里提几点:
为需要保证实时性的任务提供更高优先级的线程。有效地使用网络至关重要,尽可能通过减少调度事务以及使用高效的 API 来消除开销成本。网络传输是直播场景的能耗大头。对繁重开销大的线程任务进行分析,尝试优化线程执行任务的复杂度。例如避免不必要的数据拷贝,降低算法逻辑的时间控件复杂度(算法实时性简化),或者利用SIMD指令集进行并行化处理;以上这些手段的最终目的就是降低 CPU 在单个函数/线程上的执行周期。结语
遇到这类案例,通常情况下解释难度较大,给出直播声音卡顿、爆音是由于 iOS 发热降频造成的这类结论,天然会给人一种甩锅的感觉。所以,我们更应该充分分析、拿出证据,在分析的过程中自身可以获得很多,也解决了实际问题,从一个小点展开去分析,发现越深入越有意思,很多点都可以深入挖掘。
关于字节跳动视频云技术团队
字节跳动视频云技术团队,负责字节跳动旗下抖音、西瓜视频、今日头条等多个业务视频点播、视频直播、Web多媒体等音视频技术方案建设、播放体验优化等工作,为数亿用户提供优质的音视频服务体验。
字节跳动视频云技术团队还致力于研究、探索多媒体领域的前沿技术,参与国际、国内多媒体方向标准化工作,为多媒体内容分析、处理、压缩、传输、创新交互等领域提供软硬件解决方案,众多创新算法已经广泛应用在抖音、西瓜视频等产品点播、直播、实时通信、图片等多媒体业务,并向火山引擎企业级客户提供技术服务。