首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >导致android 手机 Jank 的元凶

导致android 手机 Jank 的元凶

作者头像
用户9732312
发布于 2022-05-13 20:11:37
发布于 2022-05-13 20:11:37
2.1K0
举报
文章被收录于专栏:ADAS性能优化ADAS性能优化

Application thread scheduler delay

Scheduler delay:应用程序是可运行状态(Runable),但在相当长的时间内不会运行。Scheduler delay 可能会引起Jank。不同性质的线程对Scheduler delay 要求不同,

  • 应用程序中的helper thread 可能会延迟许多毫秒而不会出现问题。
  • 应用程序的UI线程可能能够承受1-2ms的抖动。
  • 驱动程序kthread在运行前可运行500us,则可能会引起问题。

可以在systrace中通过线程正在运行的段前面的蓝色条来标识可运行时间。可运行时间还可以由sched_wakeup线程 sched_switch事件与发出线程执行开始信号的事件之间的时间长度来确定。

Threads that run too long

如果应用程序UI线程长时间在可运行状态,则可能会导致问题。有如下的方法可以解决长时间的Runable 状态:

  1. 使用cpusets,把关键线程scheduler 到大CPU 上。
  2. 增加CONFIG_HZ值。
    • 历史上,在arm和arm64平台上,该值已设置为100。但是,这是历史的偶然事件,对于交互式设备而言并不是一个很好值。CONFIG_HZ = 100表示a jiffy 为10毫秒长,这意味着CPU之间的负载平衡可能需要20毫秒(两个 jiffy)才能发生。这样会可能会使Jank 更容易发生。
    • 如果在更改CONFIG_HZ后确实发现功耗或性能问题显着增加,如CONFIG_HZ = 300,那么有很大可能是您的某些驱动程序正在使用基于原始抖动(jiffy )而不是毫秒的计时器。那么请修改相关的代码,清除基于jiffy 的计时。

Interrupt traffic

由于ARM平台默认情况下由CPU 0来处理中断,因此我们建议使用IRQ平衡器,来平衡IRQ 的处理。

在debug过程中,我们看到了造成问题的根源可能是CPU 0中断过多。例如,如果display 线程是在CPU 0上调度的,然而由于CPU0 处理了大量的中断,而导致display 长时间的Runable ,而display 需要在很短的时间内完成时间的工作,如果中断程序过多,那么display 线程将不会得到及时调度而产生Jank。最初,我们尝试通过将display 线程的CPU亲和力设置为CPU 1-3来解决此问题,以避免与中断争用,但是后来我们意识到尚未启用irqbalance。启用irqbalance后,由于减少了其他中断的竞争,即使display 和display 中断都在同一CPU上,jank也得到了明显改善。

可以通过查看systrace中sched和irq部分,来查找问题。sched部分显示了已调度的内容,但是irq部分中的重叠区域表示在该时间内正在运行中断,而不是正常调度的进程。如果您看到中断期间花费了大量时间,则可以选择以下选项:

  • 使中断处理程序更快。
  • 首先防止发生中断。
  • 将中断频率更改为与其他可能干扰的常规工作(如果是常规中断)不同频率。
  • 直接设置中断的CPU亲和力,并防止其平衡。
  • 设置中断要干扰的线程的CPU亲和力以避免该中断。
  • 依靠中断平衡器将中断移至负载较小的CPU。

通常不建议设置CPU亲和力,但在特定情况下可能有用。通常,很难预测大多数常见中断的系统状态,但是如果您有一组非常特殊的条件来触发某些中断,这些中断会导致系统比正常情况下更受限制(例如VR),则显式CPU亲和力可能是一个很好的解决方案。

Long softirqs

当softirq运行时,它将禁用抢占。softirqs也可以在内核中的许多地方触发,并且可以在用户进程内部运行。如果有足够的softirq活动,则用户进程将停止运行softirq,ksoftirqd将唤醒以运行softirq并进行负载平衡。通常,这很好。但是,一个很长的softirq可能会对系统造成严重破坏。

例如:Janks issue 打开Wi-Fi stream

我们看到VR性能不一致。我们在systrace中发现,单个NET_RX softirq可以运行30毫秒以上。最终,这被跟踪到了接收数据包导向功能,这是一种Wi-Fi功能,可将许多NET_RX软irq合并为一个软irq。在正确的条件下,生成的softirq可能具有非常大的(可能无限制的)运行时。

尽管此功能可能减少了用于网络的总CPU周期,但它阻止了系统在正确的时间运行正确的事情。禁用该功能不会影响网络吞吐量或电池寿命,但它确实通过允许ksoftirqd负载平衡softirq而不是规避它来解决了VR跟踪问题。

softirq在systrace的irq部分中可见,如果看到softirq运行时间过长,请检查softirq的实现。

Drivers leaving preemption or IRQs disabled too long

禁用抢占或中断时间过长(数十毫秒)会导致Jank。通常,此问题表现为一个线程变得可运行,但未在特定CPU上运行,即使该可运行线程的优先级(或SCHED_FIFO)明显高于其他线程。

  • 如果可运行线程是SCHED_FIFO,而正在运行线程是SCHED_OTHER,则正在运行的线程已被抢占或禁用了中断。
  • 如果可运行线程的优先级(100)比运行线程(120)高得多,那么如果可运行线程不在两个中断中运行,则正在运行的线程可能已抢占或禁用了中断。
  • 如果可运行线程和运行线程具有相同的优先级,则如果可运行线程不在20ms内运行,则运行线程可能已抢占或禁用了中断。
  • 运行中断处理程序会阻止您处理其他中断,这也会禁用抢占。

Incorrect use of workqueues

中断处理程序通常需要执行可以在中断上下文之外运行的工作,从而使工作可以移植到内核中的不同线程。驱动程序开发人员可能会注意到内核具有非常方便的全系统异步任务功能,称为工作队列workqueues ,并且可以将其用于与中断有关的工作。

但是,工作队列可能会导致Jank,因为它们始终是SCHED_OTHER。许多硬件中断是性能的关键,必须立即运行。工作队列不能保证何时运行。每当我们看到关键性能路径上的工作队列时,无论使用哪种设备,都可能导致Jank。在配备旗舰处理器的上,我们看到,如果设备处于负载状态,则单个工作队列最多可能会延迟7毫秒,具体取决于调度程序行为和系统上运行的其他操作。

需要工作在单独线程中的类似中断的工作的驱动程序应该创建自己的SCHED_FIFO kthread,而不是工作队列。

Framework lock contention

Framework lock争用可能是造成问题或其他性能问题的根源。它通常是由ActivityManagerService锁引起的,但也可以在其他锁中看到。例如,PowerManagerService锁定会影响屏幕的性能。如果您在设备上看到此错误,则没有好的修复方法,因为只能通过对Framework 进行体系结构改进来加以改进。但是,如果您要修改在system_server内部运行的代码,那么避免长时间持有锁,尤其是ActivityManagerService锁,至关重要。

fd contention within a process

这是罕见的。

就是说,如果您在一个进程中有多个线程在编写相同的fd,则有可能看到该fd的争用。

Unnecessary CPU idle transitions

在处理IPC(尤其是多进程)时,通常会看到以下运行时行为的变化:

  1. 线程A在CPU 1上运行。
  2. 线程A唤醒线程B。
  3. 线程B开始在CPU 2上运行。
  4. 线程A立即进入睡眠状态,待线程B完成当前工作后被线程B唤醒。

开销的常见来源介于步骤2和3之间。如果CPU 2处于空闲状态,则必须使其回到活动状态,然后线程B才能运行。根据SOC和空闲状态的深度,线程B开始运行之前,可能需要数十微秒。如果IPC每一侧的实际运行时间足够接近开销,则CPU空闲转换会大大降低该管道的整体性能。Android最常见的问题是与 binder transactions有关,许多使用 binder transactions的服务最终看起来像上述情况。

首先,wake_up_interruptible_sync()在内核驱动程序中使用该功能,该功能要调度器支持。 binder transactions现在使用此功能,它对同步 binder transactions很有帮助,避免了不必要的CPU空闲转换。

其次,确保您的cpuidle过渡时间是优化过的,并且cpuidle调速器已正确考虑了这些时间。如果您的SOC进入或退出最深的空闲状态,则无法通过进入最深的空闲状态来节省功耗。

Logging

不好的log 对性能影响较大,请删除所有调试日志。

I/O issues

I / O操作是Jank的常见来源。如果线程访问内存映射文件,并且页面不在页面高速缓存中,则它将发生故障并从磁盘读取页面。这会阻塞线程(通常持续10毫秒以上),并且如果它发生在UI渲染的关键路径中,则可能会导致jank。这里有太多的I / O操作原因需要讨论,但是在尝试改善I / O行为时请检查以下位置:

  • PinnerService。在Android 7.0中添加的PinnerService使框架可以锁定页面缓存中的某些文件。这会删除内存以供其他任何进程使用,但是如果有一些已知的先验文件可以定期使用,则可以有效地锁定那些文件。 通常,建议loaded 下面的文件,这些文件经常被大多数应用程序和system_server使用,因此不应将其分页。特别是,我们发现,如果其中的任何一个页面被调出,它们将被调回页面,并且在从重量级应用程序切换时会造成垃圾。
    • /system/framework/arm64/boot-framework.oat
    • /system/framework/oat/arm64/services.odex
    • /system/framework/arm64/boot.oat
    • /system/framework/arm64/boot-core-libart.oat
  • 加密。I / O问题的另一个可能原因。我们发现,与基于CPU的加密或使用可通过DMA访问的硬件模块相比,嵌入式加密可提供最佳性能。最重要的是,内联加密减少了与I / O相关的抖动,特别是与基于CPU的加密相比。由于对页面缓存的提取通常位于UI呈现的关键路径中,因此基于CPU的加密在关键路径中引入了额外的CPU负载,这不仅增加了I / O提取的抖动,还增加了抖动。 Aggressive small task packing 一些调度程序提供了将小任务打包到单个CPU内核上的支持,以通过使更多CPU保持更长的空闲时间来降低功耗。尽管这对于吞吐量和功耗非常有效,但对延迟而言可能是 灾难性的。在UI渲染的关键路径中有几个短期线程可以被认为很小。如果这些线程在缓慢迁移到其他CPU时被延迟,则将 导致混乱。我们建议非常谨慎地使用小任务打包。

Page cache thrashing

有足够可用内存的设备在执行长时间运行的操作(例如打开新应用程序)时可能会突然变得非常缓慢。对该应用程序的跟踪可能表明它在特定运行期间始终被I / O阻止,即使该应用程序通常未在I / O中被阻止。这通常是页面缓存抖动的迹象,尤其是在内存较少的设备上。

识别此错误的一种方法是使用pagecache标记获取systrace,并将该跟踪输入到脚本 system/extras/pagecache/pagecache.py。pagecache.py将文件映射到页面缓存的单个请求转换为每个文件的汇总统计信息。如果发现文件中读取的字节数多于磁盘上该文件的总大小,则肯定是页面高速缓存崩溃了。

这意味着工作负载(通常是单个应用程序加上system_server)所需的工作集大于设备上的页面缓存可用的内存量。结果,随着工作负载的一部分在页面缓存中获取所需的数据,将在不久的将来使用的另一部分将被驱逐并必须再次获取,从而导致问题再次发生,直到加载为止已经完成。当设备上没有足够的可用内存时,这是性能问题的根本原因。

没有万无一失的方法来修复页面缓存抖动,但是有几种方法可以尝试在给定设备上改善此问题。

  • 在持久性进程中使用较少的内存。持久性进程使用的内存越少,应用程序和页面缓存可用的内存就越大。
  • 审核设备的分离度,以确保不会不必要地从操作系统中删除内存。我们已经看到了这样的情况,即用于调试的分割区意外地留在了内核配置中,占用了数十兆的内存。这可以在击打页面缓存和不击打页面缓存之间产生区别,尤其是在内存较少的设备上。
  • 如果在关键文件的system_server中看到页面缓存崩溃,请考虑固定这些文件。这将增加其他地方的内存压力,但可能足以修改行为以避免抖动。
  • 重调lowmemorykiller以尝试释放更多的内存。lowmemorykiller的阈值基于绝对可用内存和页面缓存,因此,提高杀死给定oom_adj级别的进程的阈值可能会导致更好的行为,但会增加后台应用程序的终止时间。
  • 尝试使用ZRAM。
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-11-26,如有侵权请联系 cloudcommunity@tencent.com 删除
目录
  • Threads that run too long
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档