一 问题发现
浏览内核CPU 占用问题是影响用户使用的关键因素,会引起操作卡顿、手机耗电以及应用
崩溃等问题。CPU 占用由于受到页面、机型、应用框架的影响,较难进行定位和优化。在浏览内核性能测试中发现CPU 占用高于之前版本,通过分析CPU 曲线,发现可能是部分场景CPU 没有收敛导致的,因此对CPU 收敛做了专项测试。
老测试方案——内核之前针对CPU 收敛的测试是通过加载用户访问Top 100 的页面,应用放后台通过TOP 命令观察CPU 收敛情况。
缺点: 1.测试工作量非常大;2.Top页面结构复杂很难分析是页面中的那个元素引起的不收敛现象;3.页面经常变化,收敛状态难以稳定复现;
新测试方案——通过对页面的整理分析和与RD 的沟通,引起CPU 不收敛的问题主要是页面中的动态元素,如CSS动画、 视频播放、 Timer 定时器、 js 加载、 gif动图等,并由QA自己开发动态元素的测试页面进行测试,在测试场景上选择了应用切后台和页面切换至不可见窗口两种。
优点:1.测试工作量小,针对性强;2.页面固定、元素简单,易于复现;3. 场景更加贴近用户使用;
测试结果:
使用新测试方案完成测试后,发现如下几个问题:
问题一 页面切换至不可见窗口场景:测试中心、CSS、H5、GIF 等多个页面不收敛;
问题二 APP 切后台场景:测试中心页面不收敛;
二 初步分析
问题一通过对测试结果的分析,当用户创建空白页面,使测试页面切换至不可见窗口的场景时,包含GIF 图、CSS 动态元素的页面CPU 仍然有波动,而没有收敛至0, 这里可以猜测出可能当页面不可见时,页面的绘制没有进行pause,内核判断页面动态元素有更新,因此还在不断的进行绘制,导致CPU 不为0。
问题二测试中心页面不收敛,该页面是测试平台的导航页面,本身没有动态元素,但是仍有不收敛现象。QA通过绘制工具systrace分析页面加载过程的数据,发现切换切换后台后,内核的main(scheduler)/thread_proxy(cc)在工作,还在绘制内容。推测有两个可能:1.APP切后台,没有pause当前的webview;2.测试中心首页的某些逻辑导致内核没被pause。
三 业务和代码分析
内核加载过程——浏览内核加载页面的网络部分向网络发起请求并把网页资源下载给Loader,之后 HTML Parser 将 HTML 和 Script 解析成 DOM 树,再结合 CSS 层叠样式表生成对应的 Render 树。Render 树上就包含了每个元素的各种属性字体、颜色、屏幕上面的坐标、长、宽等。Render 树进一步生成 Graphics Context 交给平台相关的图形库把页面展示在屏幕上。大体过程是如下图这样,实际上内核是边加载边绘制的。
内核绘制过程——浏览内核为了减少不必要的绘制,保证页面的绘制速度和页面滑动的流畅度,对Render Tree 进行了分层,可以更方便的进行页面的局部更新。有更新的layer 将更新数据经过合成composite阶段,将真正的需要绘制区域数据给GL 进行draw,最终完成图像的显示。整个过程是多线程的,通过消息来驱动这个流程。
动态元素由于更新频率高,通常会划分为单独的layer,如果这个layer 的更新消息没有暂停,就会导致这个消息驱动layer 的合成,进行更新区域的计算,调用drawGL 进行图像的draw。因此CPU 会一直被占用。
四 真相大白
问题一
页面动态元素不收敛,通过将页面中动态元素删除后,测试结果为收敛,由此可以证明确实是动态元素导致。动态元素有频繁的更新消息,使得不断对包含动态元素的layer 进行合成和绘制,导致CPU 不收敛。首先需要查看该webview 是否是否被pause,webview 被切换至不可见状态时,策略上应该暂停一切页面的处理,节约更多的CPU和内存资源保证当前活动窗口的处理。
// webview onPause 的处理 public void onPause() { if (mIsPaused || mNativeAwContents == 0) return; mIsPaused = true; nativeSetIsPaused(mNativeAwContents, mIsPaused); }
内核没有对文档解析、js 加载、动图进行暂停。
解决方案:
// 网络加载暂停 void ContentViewCoreImpl::SetIsPaused(JNIEnv* env, jobject obj, bool paused) { GetWebContents()->Send(new ViewMsg_SetIsPaused( GetWebContents()->GetRoutingID(), paused)); } // 绘制暂停 void RenderViewImpl::OnSetIsPaused(bool paused) { if (!webview()) return; webview()->setIsPaused(paused); } // 文档解析暂停 void WebViewImpl::setIsPaused(bool paused) { Document* document = page()->mainFrame()->document(); if (!document) return; if (paused) document->suspendScheduledTasks(); else document->resumeScheduledTasks(); }
问题二
测试中心页面不能收敛,此页面并没有动态元素,因此用上面的方法分析是不奏效的,需要找其他的方法。百度浏览内核是基于Blink 35版本再次开发的版本,因此想要验证下原生内核是否有问题,于是验证了Blink 35、36都是不收敛,blink 37 版本可以收敛,因此可以断定是Blink 内核自己的bug,通过对绘制过程相关代码的查找,发现google 工程师对此问题的一个注释。
if (RenderView* view = renderView()) { ASSERT(!view->needsLayout()); view->compositor()->updateCompositingLayers(); // FIXME: we should not have any dirty bits left at this point. Unfortunately, this is not yet the case because // the code in updateCompositingLayers sometimes creates new dirty bits when updating direct compositing reasons. // See crbug.com/354100. view->compositor()->scheduleAnimationIfNeeded(); }
通过上面的注释可以看出,在layer 有更新进行合成过程中,会引入脏数据导致layer 一直更新,并进行后面的数据处理和绘制,导致CPU 不收敛,通过增加打印Log 也可以证明,不收敛的页面确实在不断进行该函数的调用。
通过对Blink 37 修复的patch 进行研究,内核对判断Layer 更新的状态进行了修改,调整了更新判断的结构,删除了scheduleAnimationIfNeeded 方法,通过其他状态判断是否需要更新layer,并进行数据的合成进行绘制,通过将37 patch 合入测试发现可以有效解决不收敛问题。
解决方案:
增加了CompositingReasonFinder::requiresCompositingForPosition 方法判断是否需要进行合成。
脏数据的问题还是没有根本解决,只是增加了判断,不会出现之前的循环处理。
void RenderLayerCompositor::assertNoUnresolvedDirtyBits() { ASSERT(!compositingLayersNeedRebuild()); ASSERT(!m_needsUpdateCompositingRequirementsState); ASSERT(m_pendingUpdateType == CompositingUpdateNone); ASSERT(!m_rootShouldAlwaysCompositeDirty); ASSERT(!m_needsToRecomputeCompositingRequirements); }
五 总结
CPU 收敛在CPU 性能测试中是一个非常重要的项目,对于用户体验的影响也是非常大。这个案例首先在CPU 收敛的测试方案上有很大的突破,通过较小的成本发现了不收敛的场景;其次,通过测试页面和弱网环境稳定复现bug,帮助RD定位问题,最后通过查阅blink 内核的官方文档、代码记录和bug 系统,最终找到了解决方案, 提升了整体产品的体验。
百度MTC是业界领先的移动应用测试服务平台,为广大开发者在移动应用测试中面临的成本、技术和效率问题提供解决方案。同时分享行业领先的百度技术,作者来自百度员工和业界领袖等。
>>如有问题,欢迎与我沟通
原创文章,作者:3628473679,如若转载,请注明出处:https://blog.ytso.com/193597.html