1.如何用Python写一个贪吃蛇AI
2.天津教学直播系统搭建
3.ç¨åºåå¦çå¿
å¤çå
大软件
4.Android性能优化:定性和定位Android图形性能问题——以后台录屏进程为例
如何用Python写一个贪吃蛇AI
如何用Python写一个贪吃蛇AI
前言
最近在网上看到一张让人涨姿势的,中展示的屏源是贪吃蛇游戏,估计大部分人都玩过。到非但如果仅仅是录屏贪吃蛇游戏,那么它就没有世差含什么让人涨姿势的源码地方了。问题的非录上帝的左手指标源码关键在于,中的屏源贪吃蛇真的很贪吃XD,它把矩形中出现的到非食物吃了个遍,然后华丽丽地把整个矩形填满,录屏真心是源码看得赏心悦目。作为一个CSer,非录第一个想到的屏源是,这东西是到非写程序实现的(因为,一般人干不出这事。录屏果断是源码要让程序来干的)第二个想到的是,写程序该如何实现,该用什么算法?既然开始想了,就开始做。因为Talk is cheap,要show me the code才行。 (从耗子叔那学来的)开始之前,让我们再欣赏一下那只让人涨姿势的贪吃蛇吧:(如果下面的动态浏览效果不佳的话,可以右键保存下来查看)
语言选择
Life is short, use python!所以,根本就没多想,直接上python。最初版本先让你的程序跑起来首先,我们第一件要做的就是先不要去分析这个问题。你好歹先写个能运行起来的贪吃蛇游戏,然后再去想AI部分。这个应该很简单,cc++也就百来行代码(如果我没记错的话。不弄复杂界面,直接在控制台下跑),python就更简单了,去掉注释和空行,5、行代码就搞定了。而且,最最关键的,这个东西网上肯定写滥了,你没有必要重复造轮子,去弄一份来按照你的意愿改造一下就行了。
简单版本
我觉得直接写perfect版本不是什么好路子。因为perfect版本往往要考虑很多东西,直接上来就写这个一般是bug百出的。所以,一开始我的目标仅仅是让程序去控制贪吃蛇运动,让它去吃食物,仅此而已。现在让我们来陈述一下最初的问题:
在一个矩形中,每一时刻有一个食物,贪吃蛇要在不撞到自己的条件下,找到一条路(未必要最优),然后沿着这条路运行,去享用它的美食。
我们先不去想蛇会越来越长这个事实,问题基本就是,给你一个起点(蛇头)和一个终点(食物),要避开障碍物(蛇身),从起点找到一条可行路到达终点。
我们可以用的方法有:BFSDFSA*只要有选择,就先选择最简单的方案,我们现在的目标是要让程序先跑起来,优化是后话。so,从BFS开始。我们最初将蛇头位置放入队列,然后只要队列非空,就将队头位置出队,然后把它四领域内的4个点放入队列,不断地循环操作,直到到达食物的位置。这个过程中,我们需要注意几点:
1.访问过的点不再访问。
2.保存每个点的父结点(即每个位置是从哪个位置走到它的,这样我们才能把可行路径找出来)。
3.蛇身所在位置和四面墙不可访问。
通过BFS找到食物后,只需要让蛇沿着可行路径运动即可。这个简单版本写完后,贪吃蛇就可以很欢快地运行一段时间了。看图吧:(不流畅的感觉来自录屏软件@_@)
为了尽量保持简单,我用的是curses模块,直接在终端进行绘图。从上面的动态可以看出,每次都单纯地使用BFS,最终有一天,云栖社区源码贪吃蛇会因为这种不顾后果的短视行为而陷入困境。而且,即使到了那个时候,它也只会BFS一种策略,导致因为当前看不到目标(食物),认为自己这辈子就这样了,破罐子破摔,最终停在它人生中的某一个点,不再前进。(我好爱讲哲理XD)
BFS+Wander
上一节的简单版本跑起来后,我们认识到,只教贪吃蛇一种策略是不行的。它这么笨一条蛇,你不多教它一点,它分分钟就会挂掉的。所以,我写了个Wander函数,顾名思义,当贪吃蛇陷入困境后,就别让它再BFS了,而是让它随便四处走走,散散心,思考一下人生什么的。这个就好比你困惑迷茫的时候还去工作,效率不佳不说,还可能阻碍你走出困境;相反,这时候你如果放下手中的工作,停下来,出去旅个游什么的。回来时,说不定就豁然开朗,土地平旷,屋舍俨然了。Wander函数怎么写都行,但是肯定有优劣之分。我写了两个版本,一个是在可行的范围内,朝随机方向走随机步。也就是说,蛇每次运动的方向是随机出来的,总共运动的步数也是随机的。Wander完之后,再去BFS一下,看能否吃到食物,如果可以那就皆大欢喜了。如果不行,说明思考人生的时间还不够,再Wander一下。这样过程不断地循环搜笑进行。可是就像“随机过程随机过”一样,你“随机Wander就随机挂”。会Wander的蛇确实能多走好多步。可是有一天,它就会把自己给随机到一条死路上了。陷入困境还可以Wander,进入死胡同,那可没有回滚机制。所以,第二个版本的Wander函数,我就让贪吃蛇贪到底。在BFS无解后,告诉蛇一个步数step(随机产生step),让它在空白区域以S形运动step步。这回运动方向就不随机了,而是有组织有纪律地运动。先看图,然后再说说它的问题:
没错,最终还是挂掉了。S形运动也是无法让贪吃蛇避免死亡的命运。贪吃蛇可以靠S形运动多存活一段时间,可是由于它的策略是:
1. 目标是食物时,走最短路径
2. 目标是蛇尾时,走最长路径
那第三种情况呢?与食物和蛇尾都没路径存在的情况下,这个时候本来就只是挑一步可行的步子来走,最短最长关系都不大了。至于人为地让蛇走S形,我觉得这不是什么好策略,最初版本中已经分析过它的问题了。 (当然,除非你想使用最最无懈可击的那个版本,就是完全不管食物,让蛇一直走S,然后在墙边留下一条过道即可。这样一来,蛇总是可以完美地把所有食物吃完,然后占满整个空间,c 监控源码可是就很boring了。没有任何的意思)
上面还提到一个问题:因为食物是随机出现的,有没可能出现无解的布局?答案是:有。我运行了程序,然后把每一次布局都输出到log,发现会有这样的情况:
# # # # # # #
* * * * * #
* * - 0 * #
* * # + * #
* * * * * #
* * * * * #
# # # # # # #
其中,+号是蛇头,-号是蛇尾,*号是蛇身,0是食物,#号代表空格,外面一圈#号代表墙。这个布局上,食物已经在蛇头面前了,可是它能吃吗?不能!因为它吃完食物后,长度加1,蛇头就会把0的位置填上,布局就变成:
# # # # # # #
* * * * * #
* * - + * #
* * # * * #
* * * * * #
* * * * * #
# # # # # # #
此时,由于蛇的长度加1,蛇尾没有动,而蛇头被自己围着,挂掉了。可是,我们却还有一个空白的格子#没有填充。按照我们之前教给蛇的策略,面对这种情况,蛇头就只会一直追着蛇尾跑,每当它和食物有路径时,它让虚拟的蛇跑一遍发现,得到的新布局是不安全的,所以不会去吃食物,而是选择继续追着蛇尾跑。然后它就这样一直跑,一直跑。死循环,直到你按ESC键为止。由于食物是随机出现的,所以有可能出现上面这种无解的布局。当然了,你也可以得到完满的结局,贪吃蛇把整个矩形都填充满。上面的最后一个问题,暴力法是否能得到最优序列。从上面的分析看来,可以得到,但不能保证一定得到。最后,看看高瞻远瞩的蛇是怎么跑的吧:
矩形大小*,除去外面的边框,也就是8*。Linux下录完屏再转成GIF格式的,优化前多M,真心是没法和Windows的比。用下面的命令优化时,有一种系统在用生命做优化的感觉:
Shell
convert output.gif -fuzz % -layers Optimize optimised.gif
最后还是拿到Windows下用AE,三下五除二用序列合成的动态 (记得要在format options里选looping,不然是不会循环播放的)
Last but not least如果对源代码感兴趣,请戳以下的链接:
Code goes here
另外,本文的贪吃蛇程序使用了curses模块,类Unix系统都默认安装的,使用Windows的童鞋需要安装一下这个模块,送上地址:
需要curses请戳我
以上的代码仍然可以继续改进(现在加注释不到行,优化一下可以更少),
天津教学直播系统搭建
4、功能文本文档:产品运营刚开始依据客户常说实际功能开展需求分析文档的撰写,把每一个端、每一个功能,都用文本的方法记下来,另外绘制思维脑图,从主页刚开始对每一个功能开展细分化,绘制思维脑图;5、明确功能:进行需求分析文档和思维脑图后客户开展核查,明确准确无误后进到下一阶段;6、UI设计:客户明确要求后UI设计师依据要求内容将视频直播APP每一个网页页面都以照片的方法开展展现,并将搞好的UI发与客户确定,客户明确提出改动,改动后明确UI;7、功能工作交接:要求、思维导图、UI明确后将分配任务给技术性,并开会研究实际功能,步骤等都了解掌握后进到开发环节,技术性刚开始对APP开展构建;8、申请办理第三方:产品运营依据技术性出示所需第三方开展第三方的申请办理;9、租用服务器:直播间APP开发到一定环节能够开展网络服务器的grpc插件源码选购,选购后立即将视频直播APP布署到网络服务器,网络服务器开发工作人员刚开始构建;、出测试包:视频APP开发基础进行之后出一个测试包,功能基础都完成,测试组工作人员根据不一样型号不一样方法对视频APP开展兼容模式测试,天津教学直播系统搭建,天津教学直播系统搭建、工作压力测试、流畅度测试,天津教学直播系统搭建、逻辑性测试、步骤测试等,把测试结果意见反馈技术性。现在的直播这么火,流量入口,广告新星,社群互动,拥有太多的可能。天津教学直播系统搭建
在线教育系统源码也越来越受欢迎,那么该如何使用在线网校系统源码搭建在线教育平台教育直播软件开发重点,你知道吗?尽量减少在使用过程中对资源的占用,总体来说控件越少、合理布局层级越淡,直播软件整体的性能就会越好。假如让客户发觉你的应用软件非常消耗耗电量,那么客户就容易就会卸载掉应用。在线教育软件开发的优势与劣势受形式影响,加快了用户对使用线上教育服务的习惯培养,线上教育已经迎来了更高的发展契机,甚至开始倒逼传统的教育机构改变固有的教育模式。在线教育软件开发可以做到随时随地,灵活有效,个人掌握学习主动权,只要有…在线教育平台开发迫在眉睫在互联网快速发展的当代,教育行业也不甘落后,都紧追时代潮流,不断改良教育教学方式,所以出现了许多在线教育平台,在线教育的兴起,让人们的学习变得更加方便,更加快捷,在线教育平台开发的脚步也越来越快。在线教育平台…在线教育平台搭建要更加个性化说到双师教学,一些人已经听说过了,因为这是在许多的大城市里面,近阶段兴起的一种非常流行的教学模式,但是对于许多人来说,并不是非常了解,那么到底什么是双师教学呢?在线教育平台搭建中双师教学的出现。天津手机直播系统哪家好音视频实时互动是直播系统开发中的关键。
再就是这些人是有一定的知识基础的,也有自律能力,都是为了得到提高和丰富学习质量传统教育受到限制,搭建网校平台显得尤为重要想要抓住流量红利,线上线下深度融合,搭建网校平台我们需要做以下工作:对于传统的线下机构来说,老师的教学能力应该是不会受到质疑的,因为本质上来看,线上线下的教学内容是相似的。老师如何通过网络表现自己,让学员…做SaaS系统的教育公司与传统在线教育的区别在线教育已经到达了一个新的高度,特别是今年,在线教育更是蓬勃发展,因此出现了许多平台想要进入教育行列,许多伙伴也会问我如果是自己搭建在线教育平台那么与做SaaS系统的教育公司系统的区别是什么,哪种好搭建网校平台与传统教育有大不同搭建网校平台已经成为教育机构吸引用户的好方法,因为在线教育可以合理分配学习资源,可以让学生学习更加省劲,不必再早起挤公交、地铁去学校学习,机构也可以省去租赁场地的烦恼,所以搭建网校平台是有利而无害的,在家长层…在线教育在线网校系统源码在教育系统搭建中的作用教育的形式多种多样,如今,在线教育平台已经屡见不鲜,几乎我们城市的各个角落和网络上都是一抓一大把,这从另一方面反映出大家对教育的重视程度。
短视频的火爆是源于短视频程序源码所开发的功能特殊在短视频程序源码的开发里很关键的是搭建平台首页观看页面的功能布局,要保证有逻辑性和技术性搭建主页的视频顺序及长短。能够立在用户的视角,用与众不同的目光和严苛的技术性来进一步加深视频内容的画面感和意境,开发出…直播间源码的开发为什么需要做市场定位?二、直播间源码的开发为什么需要做市场定位?所以说定位的关键环节是用户而不是产品,了解用户需要什么就做什么这才是很好的方法。解读直播带货源码开发的直播带货系统本质2、直播的活动页源码形式更改了原始电商图文静态展示产品的情况,直播方式更加形象。2、网络红人的出现逐渐开启了网红营销模式,促进了网红营销这一新变现方式的出现。2、相对于传统电商模式,直播带货模式在产品呈现形式、时…直播软件源码的开发是如何渗入我们生活点滴中的?送走了共享经济,我们又迎来了直播经济,直播软件开发的受到了当下社会的欢迎,直播软件源码所开发的各类直播系统已成为我们生活娱乐的一种方式,直播软件源码的开发是有哪些亮点吸引着我们。直播一对一源码的开发过程中需要的基本功能以往我们看到的运营中的直播平台,分类列表大多按照主播的才艺进行分类。而一个直播APP开发项目,在人员上的投入是比较大的,从开发前期的项目谈判,到后期的开发测试。
赛新科技——直播系统运营的流程方法1、要求沟通交流:客户与产品运营沟通交流视频直播APP的大概功能,必须哪些端,新项目开发时间和额度等,明确后就可以进到下一步;7、功能工作交接:要求、思维导图、UI明确后将分配任务给技术性,并…赛新科技——影响直播系统开发价格的因素主要特性相似liveAPP发展:访问在线视频,在线视频直播,在线评估共享,视频和动画效果写作,关键控制功能模块的小音像店,用户中心,如果功能要求非常复杂,优化结构的网页是更实用,流畅的高,所以一定要选择一家专…直播系统平台搭建的主要方向现在的直播行业可是朝阳产业,毕竟也是现代化进程的结果,其实很火的直播应该在年到年之间,当时只有游戏直播,到现在发展到各个环节,这说明了直播已经完全融入了人们的日常生活中,于是很多人想尝试下直播平台搭建…直播系统开发架构方案该怎么做?直播系统开发码薯科技有限公司支持HLS/RTMP推流等多种直播源接入,支持多终端适配,上行码率自适应;具备集美颜、音频处理技术、首屏秒开、低卡顿率、录屏直播、清晰度无缝切换等功能,满足客户多种场景需求;全平台…直播系统开发有哪几种常见的方式游戏直播的话,是现在很火的直播了。要搭建的完整的视频直播系统技术门槛很高,不是一个创业团队短期内能够做到的。武汉网校直播系统公司
CDN是直播中很贵的,一般都是采用第三方的;如果自己做的话,也需要和CDN厂商对接有经验的技术。天津教学直播系统搭建
是因为直播行业的MCN机构对网红及其推广的内容进行了广设计和独特的推广,这与互联网爆款设计思维一样。为什么网上出现这么多爆款?因为从产品设计、开发阶段,就采用了互联网产品的设计思维,紧紧抓住了互联网用户的需求和消费心理。”她说。在网络直播的生态领域,从产品设计端就已经开始创新,同时在供应链以及内容、电商整合营销方面,也进行了数字化协同创新。“平台并不只是给直播提供引流作用,更重要是要去赋能平台上的企业用户,未来大数据分析、人工智能等新技术的应用会提供非常重要的洞见。”乔晗称。直播生态系统的价值共创思维乔晗认为,在直播生态中,无论直播平台、主播、MCN机构、企业用户还是消费者用户,不能只从为自己创造价值的角度出发,而是要考虑能否为生态系统中的其他参与者创造价值,形成价值共创。她具体分析,企业对主播的选择,是基于主播对企业的价值创造来进行判断的。企业选择直播平台,也要基于平台的价值创造。平台是否能给企业提供精细的客户匹配以及是否能以其他的方式赋能企业,比如提供高质量的数据分析报告等,对于平台降低用户多归属性,提高用户忠诚度至关重要。她认为,从企业自身能力构建的角度来看。天津教学直播系统搭建
成都赛新科技有限公司办公设施齐全,办公环境优越,为员工打造良好的办公环境。赛新是成都赛新科技有限公司的主营品牌,是专业的计算机软硬件开发、销售并提供技术服务;网络技术开发;计算机系统集成;网络技术、计算机技术咨询、技术服务;软件开发;网页设计;销售办公用品及耗材;软件信息技术咨询服务。(依法须经批准的项目、经相关部门批准后方可开展经营活动)。公司,拥有自己的技术体系。公司以用心服务为重点价值,希望通过我们的专业水平和不懈努力,将计算机软硬件开发、销售并提供技术服务;网络技术开发;计算机系统集成;网络技术、计算机技术咨询、技术服务;软件开发;网页设计;销售办公用品及耗材;软件信息技术咨询服务。(依法须经批准的项目、经相关部门批准后方可开展经营活动)。等业务进行到底。自公司成立以来,一直秉承“以质量求生存,以信誉求发展”的经营理念,始终坚持以客户的需求和满意为重点,为客户提供良好的网校系统 ,在线网校系统, 在线教育系统,培训系统,从而使公司不断发展壮大。
ç¨åºåå¦çå¿ å¤çå 大软件
ç¨åºåå¦çå¿ å¤çå 大软件ç¨åºåå å¤§å¿ å¤è½¯ä»¶
%ç人é½æ²¡è§è¿
1.Giphy Capture
Giphy Cap tireæ¯ä¸ä¸ªä¸ä¸ºManç¨æ·åå¤çä¸æ¬¾GIFå¶
ä½å¨ï¼éåé常ç®åï¼é¤äºå¯ä»¥å½å±ä¹å¤ï¼è¿å¯ä»¥
对çæçGIFè¿è¡ç¼è¾ï¼ å大å°è°èãé¿åº¦æ§å¶ã
ææ¾ç顺åºçï¼å¦æä½ æ³åæç« æ¶ï¼æ³è¦å¨æç« æ
å ¥ä¸äºGIFå¨å¾æ¥è®©ä½ ç代ç åç¨åºæ´å¥½çè§£ï¼ è¿
æ¬¾å·¥å ·å°±å¾éå
2.json editor online
json editor onlineæ¯ä¸ä¸ªç®åãçµæ´»ãå¯è§åå¨çº¿ç
JSONç¼è¾å¨ï¼ æ¯æå·®å¼å对æ¯ï¼å¯æ¥çãç¼è¾å
æ ¼å¼åJSONæ°æ®ï¼ is onæ ¼å¼æ¯å¼åä¸æ¯è¾å¸¸è§çï¼
is oné 读åæ ¼å¼åå°±æ¯ä¸ä¸ªå¸¸è§çå·¥å ·ï¼ ç¨äºè¿ä¸ª
å·¥å ·ï¼å ¶ä»ççªç¶å°±ä¸é¦äº!
3.oh-my-zsh
må½ä»¤è¡æ¯ç¨åºåçæç±ï¼ æ以è¿æ¬¾æ¿ä»£bashå·¥å ·
å¿ é¡»å¾æ¿åºæ¥ï¼ ç´¢ç¶æ们çbash shellåè½ä¹å¾å¥½äº
ãä½è¿æ¯ç¥æ¾æ´ç´ ï¼æ²¡æé£ä¹çé ·ç«ï¼æ以è¿ä¸ª
oh-my-zshå°±å¼å¾ä¸æï¼ é½è¯´å®æ¯æ好ç¨çshellï¼
没æä¹ä¸ï¼å å«
+æ件ã+主é¢ãGit Hub ï¼ ææ°ï¼ ä»è¿
ä¸ç³»åçæ°åä¸å°±è½å¤æè§å°è¿ä¸ªshellå·¥å ·çå®å
以å强大ä¹å¤ï¼å°ç½ç¨åºåä¹å¯ä»¥ç¨ï¼é«ç«¯å¤§æ°ä¸
档次!
4.Process On
Process onæ¯ä¸æ¬¾å¨çº¿ä½å¾å·¥å ·ï¼ å¯å¶ä½æ维导å¾ã
æµç¨å¾ãUMLå¾ãçé¢åå设计ãç»ç»ç»æå¾çç
ä¸æä¹å¾ç®åï¼å¹¶ä¸å è´¹ãå å®è£ ï¼å¨çº¿å³å¯æ
ä½ï¼å¤§å®¶é½ç¥é身为ç¨åºåï¼å¿ é¡»æ¶å»ä¿ææè·¯æ¸
æ°ï¼è¿æ ·ä»£ç ååºæ¥å°±æ¯ååéçäºæ ï¼æ以ä½å¾
æ¯å¾æå¿ è¦çï¼åæ£è¿ä¸ªå·¥å ·æ¯çé¦!
5.Copy Translator
Copy Translatoræ¯ä¸æ¬¾é常åºè²çç¿»è¯è½¯ä»¶ï¼ å è´¹ä¸
å¼æºï¼æ¯æå åç§ä¸åè¯è¨çç¿»è¯ï¼ä¸ç®¡ä½ æ¯ç§ç
人åè¿æ¯ç®æ³å·¥ç¨å¸(å½ç¶è±æå¾å¥½çå¯ä»¥å¿½ç¥)è¿
款翻è¯è½¯ä»¶é½å¾éåï¼ç¸æ¯å ¶ä»ç¿»è¯è½¯ä»¶
Copy Transat orå¯ä»¥è§£å³ä¸äºå¤ä½çæå¥åæ¢è¡å¸¦æ¥
çä¹±ç é®é¢ï¼ä¹ä¼æ´é«æï¼
åªéå¤å¶ææ¬å°ç²è´´æ¿ï¼ä¸ä¸ç§å³å¯æ¥çç¿»è¯ç»æ
ï¼æé«å·¥ä½æçã
6.VisualStudiop Code
VisualStudio Codeæ¯ä¸ä¸ªè¿è¡ä¸MacOSXãWindows
åLinuxä¹ä¸çï¼ é对äºç¼åç°ä»£Webåäºåºç¨çè·¨
å¹³å°æºä»£ç ç¼è¾å¨ï¼å å«ææ主æµçå¼åè¯è¨çè¯
æ³é«äº®ãåªè½ä»£ç è¡¥å ¨ãèªå®ä¹çé®ãæ¬å·å¹é ã
代ç ç段ã代ç 对æ¯DiffãGitçç¹å¾ï¼ æ¯ææ件æ©
å±ï¼ å¯ä»¥éè¿å®è£ æ件æ¥æ¯æC++ãC#ãPythonã
PHPãJavaçè¯è¨ãåæ¶é对ç½é¡µå¼ååäºç«¯åºç¨å¼
åä¹åäºä¼åã
Android性能优化:定性和定位Android图形性能问题——以后台录屏进程为例
简介发现、定性与定位
总结
跟不上旋律节奏的VSYNC
严重异常耗时的dequeueBuffer
VirtualDisplay合成耗时
结论
FPS
初步定位问题
定性问题
定位问题
成果展示
参考
简介本文记录一次Android图形性能问题的分析过程——发现、定性和定位图形性能问题,以及探讨的性能优化方案。
环境:Android Q + MTK + ARM Mali-G。
所分析的性能问题(下称case):打开录屏应用并启动后台录屏,滑动前台应用(滑屏)。性能表现差:CPU、GPU负载显著升高、掉帧、用户明显卡顿感,帧率不足帧,帧渲染、合成耗时急剧飙升(渲染耗时平均为ms左右)。
经过优化后,相同环境和条件下,渲染帧率稳定在帧(提升一倍),渲染耗时平均为8.ms左右(为优化前的不到三分之一的消耗)。
关键词 Keywords: Screen Recording; Frame rate; FPS; GPU utilization; Jank; MediaProjection; VirtualDisplay; MediaCodec; Perfetto; Inferno; Surface; SurfaceTexture; VSYNC; SurfaceFlinger; HWC; Hardware composer; GPU; OpenGL;
发现、定性与定位FPS计算FPS的方法和工具 Android框架层通过hwui配合底层完成渲染。该框架本身提供了逐帧渲染分段耗时记录。通过dumpsys gfxinfo可以获取。
io.microshow.screenrecorder/io.microshow.screenrecorder.activity.MainActivity/android.view.ViewRootImpl@6b9b8a9?(visibility=0)DrawPrepare?Process?Execute3...................1................使用工具统计帧率与平均耗时(同时打印GPU负载),在开启后台录屏的情况下滑动屏幕,平均渲染耗时高达~ms,超出.ms一倍,导致帧率仅帧,显著低于帧。
Average?elapsed?.?msFPS:??│?9.?0.?.?2.#?GPU负载?LOADING?BLOCKING?IDLE?0?#?case的对比——未开启后台录屏Average?elapsed?9.?msFPS:??│?1.?0.?5.?1.通过gfx柱状图直观感受性能数据 直观地感受图形渲染性能,除了帧率感受、触控延时外,还可以通过将gfxinfo的分段耗时通过柱状图展示在屏幕上。
这是case性能问题的gfxinfo柱状图,可以看到红柱和绿柱都非常高,远远超越了流畅标准。其中,绿柱异常放大表明两个Vsync之间耗时显著增长,红柱异常放大表明应用层应用加速使用的DisplayLists大量增长、或图形层使用GLES调用GPU耗时显著增多导致的GPU执行绘制指令耗时变长。
初步定位问题本节记录初步的分析思路和定位过程。首先我们完成实验(启停后台录屏并滑动屏幕触发渲染)、观测以及记录,拿到了后台录屏启停情况下的FPS、分阶段耗时以及GPU负载(相关数据位于FPS小节)。
开发的工具输出的统计数据计算结果非常直观,一眼可见,后台录屏为Draw阶段带来额外的~8倍或~8ms耗时,给Process阶段带来额外的~2倍或~ms耗时。帧率从帧坠落到~帧。
耗时分析 可以看到,主要的额外耗时来自Draw和Process。接下来重点围绕着两part定位问题问题。
StageDescriptionCompDraw创建DisplayLists的耗时。Android的View如果支持硬件加速,绘制工作均通过DisplayLists由GPU绘制,可以处理为onDraw的耗时额外~8ms或~8倍Prepare准备没有额外耗时ProcessDisplayLists执行耗时。即硬件加速机制下提交给GPU绘制的工作耗时额外~ms或~2倍ExecuteFramebuffer前后缓冲区flip动作的耗时,上屏耗时额外不到~1msHz下,上述4个步骤合计耗时小于.ms为正常情况。case为~ms。主要增量来自Draw和Process。
经过上述初步分析、观测后,接下来的分析可以围绕Draw和Process开展。由于Android Draw部分涉及较广,包含App 渲染线程(DisplayLists)、UI线程(onDraw方法创建DisplayLists),以及图形栈耗时如SurfaceFlinger、RenderEngine等都可能增加Draw耗时。
这里一个技巧可以初步判断耗时来自App进程(渲染线程和UI线程)还是来自图形栈。如果能判断耗时来自App或图形栈,那么可以缩小分析范围、减少分析工作量。上述四大阶段的耗时统计分类比较宽,实际上还有更详细的分阶段耗时,它呈现在前文描述过的gfx统计信息柱状图上。gfx柱状图会以蓝色(RGB(,,))呈现onDraw方法创建和更新DisplayLists的耗时。如果case与正常情况对比后,这部分耗时(蓝柱大小对比)差异很小,即可说明额外的Draw耗时不是来自App的,极可能来自图形栈。Besides,结合过度绘制分析,判断case与正常情况下是否有更多的额外绘制次数可以协同判断。
——根据上述指导思想,排查出了case的额外Draw耗时与App onDraw无关,多出来的DisplayLists来自App以外的进程,可能是图形栈如SurfaceFlinger。
定性问题本小节介绍问题追踪过程,通过一些方法定位到各阶段的耗时原因,并定性地得出case性能问题的性质。从本小节开始,围绕Perfetto进行分析。这里贴出perfetto的总览,我将关键的信息排序到顶部。前四行分别为SF负责图形的线程、提交到GPU等待完成的工作、Vsync-App、Vsync-sf,最后两行为case中出现卡顿掉帧的App的主线程(UI)和渲染线程(RenderThread)。
跟不上旋律节奏的VSYNC容易看到,Vsync-sf非常不规律。Vsync-sf是触发SurfaceFlinger一次合成工作的基于Hardware VSYNC虚拟出来的一个信号。它相对于真实硬件信号(HW_VSYNC)一个规律的偏移(在case设备上,Vsync-app与Vsync-sf都被配置为8.3ms,即硬件VSYNC到达后,虚拟的Vsync-app和Vsync-sf延时8.3ms后发出,分别触发App绘制、SurfaceFlinger合成。
而case的Vsync-sf交错、残次、不齐、无规律,显然工况不佳。它将导致SurfaceFlinger不能按照预期的时间间隔将合成的帧提交到Framebuffer(经过Flip后,被提交的Framebuffer将上屏成为显示器的下一帧图像),出现掉帧/丢帧。
As we can see,case的VSYNC-sf出现严重的漂移(见图,第二行的VSYNC-sf残次不齐、跟不上规律、难看且混乱),这导致了丢帧。(但VSYNC-sf的失控仅表示与丢帧的相关性,并不直接表明因果性。)
VSYNC-sf为什么会出现偏差? 出于功耗的考虑,VSYNC-sf合VSYNC-app并不是一定会触发的。如果app或sf并没有更新画面的需求,那么死板固定地调度它们进行绘制和合成是不必的。编程上,负责触发VSYNC-sf和VSYNC-app的两个EventThread会在requestNextVsync调用后才会将下一个VSYNC-sf或VSYNC-app发出。因此,当(各自EventThread的)requestNextVsync没有调用时,VSYNC-app和VSYNC-sf也就出现漂移。BufferQueueLayer::onFrameAvailable会在应用提交后调用,该方法通过调用SF的signalLayerUpdate触发产生下一个VSYNC-sf。
换而言之,出于功耗,或别的什么原因(比如耗时导致的延期,人家是线程实现的消息队列),SurfaceFlinger的SFEventThread有可能不调用requestNextVsync,这将导致Vsync-sf在窗口期内短暂消失——但是也不会出现参差不齐的情况。结合case的VSYNC信号报告来看,VSYNC-sf信号异常切实地提示了性能问题——它的不规律现象表明前后Vsync之间有异常耗时,而非低功耗机制被激活或无屏幕刷新(case性能问题复现时一直在滑前台应用的屏,它每ms都有画面更新的需求)。
VSYNC-sf虽然出现了偏差,但是它与卡顿问题仅有相关性(或者说它是性能问题的结果),并非因果关系。猜测是其他卡顿问题导致了SF延缓了对VSYNC的request,导致其信号出现漂移。VSYNC-sf信号偏差实质上指导意义重大,因为它能提示我们,问题发生在比App更底层的地方(前文分析的结论),且比SurfaceFlinger提交到Framebuffer更上层的位置(VSYNC-sf用于触发合成,合成完成后提交到屏幕双缓冲区)。
这样,将case性能问题的上下界都确定了,问题分析范围从原先的整个图形栈,有效的缩小到了SurfaceFlinger渲染和合成阶段了。
严重异常耗时的dequeueBuffer通读Perfetto,可以看到,出了难看的Vsync-sf以外,还可以看到刺眼的超长耗时的draw(App UI线程)以及耗时变态长的dequeueBuffer(App 渲染线程)调用。相对于正常情况,perfetto报告提示的case的draw方法成倍增长的耗时非常容易被误认为耗时“居然来自一开始就排除掉的App进程",这与前文提出的”问题范围“是不能自洽的——它们是相反的结论,肯定哪里不对。仔细分析才能发现,draw方法确实是消耗了更多墙上时间(但是不意味着消耗了更多CPU时间,因为等待过程是sleep的),但是draw方法是因为等待渲染线程的dequeueBuffer造成的耗时,而dequeueBuffer的严重异常耗时却是被底层的图形栈拖累的。
我们看到,draw严重耗时,渲染线程dequeueBuffer消耗掉~ms的时间。As we all known,Android的Graphics buffer是生产者消费者模型,当作为消费者的SF来不及处理buffer并释放,渲染线程也就需要额外耗时等待buffer就绪。上面还有一段"Waiting GPU Completion"的trace没有贴上来(下图),这段耗时比不开启后台录屏的case下高得多(~3ms对比~ms),说明了一定的GPU性能问题或SF的性能问题,甚至有可能是Display有问题(HWC release耗时过长也会导致SF释放buf、生产者渲染线程dequeueBuffer额外等待)。
这里的机制比较复杂,不熟悉底层Graphics buffer的流水线模型就不好理解。In one world, dequeueBuffer申请的buffer不是凭空new出来的,而是在App-SurfaceFlinger-Framebuffer这一流水线中循环使用的。流水线中的buffer不是无限的,而是有穷的几个。当底层的伙计,如SF和HWC,使用了buffer但是没有来得及释放时(它们的工作没做完之前不会释放buffer),流水线(可以理解成头尾相接的单向队列(ring buffer))没有可用的buffer,此时dequeueBuffer就不得不进入等待,出现耗时看上去很长的问题。实际上,dequeueBuffer耗时的唯一原因几乎仅仅只有一个:底层消费太慢了,流水线没有剩余buffer,因此需要等待。
这个模型抽象理解非常简单。下图,右边消费者是底层图形栈——它每消费完一个buffer就会释放掉,每释放一个buffer应用层能用的buffer就加1。左边生产者是App渲染线程——它调用dequeueBuffer申请一个buffer以将它的画面绘制到这个buffer上。buffer送入BufferQueue后由右边的消费者(图形栈)进行消费(合成、上屏显示),然后释放buffer。当图形栈来不及release buffer时,dequeueBuffer的调用者(App渲染线程)将由于无可用buffer,就必须挂起等待了,在perfetto上就留下长长的一段”耗时“(实际上是墙上时间,大部分都没有占用CPU)。
以上,这就是为什么说App渲染线程dequeueBuffer严重耗时中的耗时为什么要打引号,为什么要说是被图形层拖累了。
下图可以看到,刨去dequeueBuffer的严重异常耗时,执行渲染的部分耗时相对于正常的case几乎没有差异,这可以断言渲染线程的惨烈耗时主要就是被dequeueBuffer浪费了。
从GPU Completion来看,此时GPU正在为SF工作,因为在图中看到(不好意思没有截全,下图你是看不出来的),dequeueBuffer总是在SF的GPU Completion结束之后结束的,这就表明SF正在通过GPU消费buffer(调用GPU进行合成后提交,然后标记buffer允许被渲染线程dequeue)。dequeueBuffer获取到就绪的buffer此时此刻取决于SF的消费能力——因为case中它是短板。(当然图形层的buffer可用不止SurfaceFlinger需要释放,因为SF释放后buffer实质上流转到更底层的HWC,等它将Buffer提交到屏幕后才会释放,这里释放后才能给App再次使用(上面哪个模型图把SF和HWC合并为流水线的图形层buffer消费者)。
从perfetto报告看HWC release非常及时、余量充足,SF的GPU Completion则较紧密地接着dequeueBuffer返回,基本断言是SF太慢了——排除HWC的责任。(下图看不出来,当时没有截图到HWC的release情况。)
到这里,除了再次确认排除了前台App的问题外,还可以断言问题来自SurfaceFlinger过分耗时。此外将问题范围的下界从整个SF合成流程(上文的Vsync-sf)缩小到了排除HWC的范围。
结论:渲染耗时一切正常,问题出现在SF消费buffer(合成图形)失速了,导致没有可用的buffer供渲染线程使用。从下图的SF的工况(第三列)来看,情况确实如此。
既然一口咬定是SF的锅,那就瞧瞧SF。先看SF的INVALIDATE,这没啥好看的,异常case和正常case都是~2.5ms。主要看refresh,正常case ~6.8ms,异常case ~.8ms。refresh包含SF的合成四件套,包括rebuildLayerStack、CalcuateWorkingSet、Prepare、doComposition。Perfetto报告直接表明,case的后台录屏导致的额外一次合成和配套工作是主要的耗时增量。
之所以会执行两次合成,是因为后台录屏工具编程上通过Android SDK提供的MediaProjection配合VirtualDisplay实现一个虚拟的镜像的屏幕。SurfaceFlinger会将画面输送一份到这个虚拟的Display以实现屏幕图像传送到录屏工具,虚拟的屏幕要求额外的一次合成。从上图可以直接得出结论,case带来的额外工作消耗就是对该录屏用的VirtualDisplay的合成工作(doComposition)带来的。
VirtualDisplay合成耗时由于问题范围已经缩小到了很小的一个范围,在SurfaceFlinger的Refresh过程中,case相对正常应用有巨大的差异耗时,几乎完全来自于对VirtualDisplay的合成耗时(doComposition)。同时也可以看到,两次合成(一次是设备的物理屏幕,一次是case的后台录屏工具创建的虚拟屏幕)中,虚拟屏幕的耗时远远高于物理屏幕(4倍以上)。
通过查看ATRACE的tag(上图,Perfetto中SurfaceFlinger中主线程的各个trace point都是用ATRACE打的tag),结合dumpsys SurfaceFlinger,能直接看到的线索是:
虚拟屏显著耗时,且合成工作通过GLES调用GPU完成
物理屏合成耗时很小,它通过HWC合成
结合图中提示的trace tag、耗时,可以得出结论,使用GPU合成的虚拟屏中因GPU合成耗时很长,导致它显著高于物理屏HWC合成耗时。如果GPU合成能够和HWC合成一样快,或者干脆让虚拟屏也使用HWC合成,那么可以预期SurfaceFlinger的合成工作的消耗将显著降低。
结论本小节综合上述三个小节的分析,对节”定性问题“下一个结论。
耗时的本质已经被看透,录屏工具申请创建的VirtualDisplay没有通过HWC进行合成,而是通过GPU进行合成,它耗时很长导致界面卡顿。In one word,case使用的VirtualDisplay的合成方式不够高效。
HWC是Hardward Composer。它接收图形数据,类似于往桌面(真的桌面,不是电脑和手机的桌面)上面叠放照片和纸张——即合成过程。这个工作能将界面上几个窗口叠加在一起后送到屏幕上显示。通过GLES调动GPU也能干这活,不过HWC执行合成的动作是纯硬件的——它很快,比GPU快几倍。
定位问题前面虽然定性了问题原因是合成方式不够高效,但是没有得出其中的原理——为什么虚拟屏不使用高效的HWC进行合成。本节通过介绍HWC的原理、SurfaceFlinger控制合成方式、虚拟屏Surface特性等来介绍图形栈中合成方式的处理模式。掌握了相关管理后,探讨一些尽量通用的共性的解决方案实现性能优化。最后着重介绍多套优化方案中的一种直面根本原因的解决方法——MediaCodec.MediaFormat创建的支持HWC合成的Surface方案。
SurfaceFlinger如何决定使用HWC还是GPU合成? SurfaceFlinger合成主要可以依靠两条路径。其中之一是”纯硬“的HWC合成(在dumpsys SurfaceFlinger中可以看到Composition type为DEVICE),另一个是通过OpenGL让GPU进行合成(Composition type为CLIENT)。
除非是功耗上的设计,否则SurfaceFlinger总是会优先检查本次合成是否支持使用HWC。编程上,在合成阶段之一的prepare过程中,SurfaceFlinger通过prepareFrame在RenderSurface与Hardware Composer(即HWC)的HIDL服务通信,完成hwc layer的创建。但是,layer能够成功创建不意味着一定支持HWC合成。SurfaceFlinger通过getChangedCompositionTypes向HWC查询不支持HWC合成的Layer。该方法返回的layer如果被标记为CLIENT合成,那么这部分Layer无法由HWC进行合成,而只能通过GPU进行合成——case的VirtualDisplay就是这个情况。
部分layer可能不能由HWC合成的原因(除功耗策略、其他软件策略外):
HWC layer达到上限 Hardware Composer支持的layer数量是有限的。查阅公开资料可知,HWC合成动作属于硬件提供的能力,它们的合成能力受到硬件本身的限制。Google官方资料对Android设备的要求是,HWC最少应该支持4个Layer,分别用于一个常规页面上最常见的4个层:壁纸、状态栏、导航栏和应用窗口。 在case设备中,经过测试,该平台的HWC最多支持7个能进行HWC合成的layer,从第8个layer开始,完完全全只能使用CLIENT合成亦即SurfaceFlinger调用RenderEngine通过OpenGL调动GPU进行合成。 正是由于HWC合成layer有上限,因此在弹出多个弹窗、叠加过于复杂时,即使界面简单也有可能出现比较明显的卡顿。
VirtualDisplay的Surface格式不受HWC支持 HWC的硬件合成能力对buffer(Surface封装)内保存的图像的格式有要求。比如,HWC不能处理缩放,仅支持一部分的格式,大多数都还有其他因素会导致不支持,如旋转、部分Alpha等等。In one word,图像格式的数量是远远多于HWC支持的类型数的。当HWC碰到不支持合成的Surface时,就会在前文提过的getChangedCompositionTypes中通知SurfaceFlinger,由SurfaceFlinger转为使用GPU合成。
结合上述几种情况,设计实验验证。其中通过在物理屏上弹窗来增加Layer以获取HWC Layer上限。确认case无法使用HWC合成不是Layer上限导致的问题后,通过对比来验证Surface格式问题。Surface是对native层的buffer的封装,其类型广泛、实现复杂,一个一个试是不现实的。通过对比性能强劲的类似实现可以一探究竟。Android adb提供一个出厂自带的录屏命令screenrecord、用于测试双屏显示功能的虚拟辅助屏幕(开发者模式-模拟辅助屏)、著名远程窥屏工具scrcpy等三个工具是一系列重要参考。
经过测试,screenrecord和scrcpy创建的VirtualDisplay支持HWC合成——这是优化目标。首先看看它们的实现。
编程上,虚拟辅助屏幕采用了与case一模一样的实现——通过创建VirtualDisplay让图形层额外合成一次屏幕到该虚拟屏幕中。虚拟屏幕本质上将画面发送给录屏功能实现,而非进行显示来完成录屏。
通读screenrecord源码,逻辑上,它与虚拟辅助屏、case录屏应用是相同的——VirtualDisplay录屏。但是编程上略有差异:
screenrecord直接通过binder与SurfaceFlinger通信,获取了raw VirtualDisplay,而