Farlanki

掌上课堂性能优化历程

字数统计: 920阅读时长: 3 min
2016/08/04 Share

苹果在WWDC 2013上介绍了drawViewHierarchyInRect:这个新的api,用于将指定view的整个view hierarchy 渲染在context中.renderInContext:是运行在app本身的地址空间里的,使用的是CPU,而drawViewHierarchyInRect:则是运行在app的地址空间之外的,使用了尽可能多的的GPU加速.
参考:http://stackoverflow.com/questions/23157653/drawviewhierarchyinrectafterscreenupdates-delays-other-animations


可以看到,在渲染复杂的包含多个UIImageView的视图时,新方法比renderInContext:的速度的确快了不少.

试水

在Drlu中,一个主要的功能就是录制屏幕视频.之前使用的方法就是renderInContext:.先用Time Profiler测试一下其运行情况:

1
2
3
4
5
6
7
var width : size_t = CGBitmapContextGetWidth(self.screenshotContext)
var height : size_t = CGBitmapContextGetHeight(self.screenshotContext)

CGContextClearRect(screenshotContext, CGRectMake(0, 0, CGFloat(width), CGFloat(height)))
self.captureLayer?.renderInContext(screenshotContext!)
self.captureLayer?.contents = nil
let cgImage : CGImageRef = CGBitmapContextCreateImage(screenshotContext)!


不看不知道,一看吓一跳.录制功能一共运行了十秒,期间CPU使用率一直在99%左右波动.renderInContext占用了39.2%的运行时间.乖乖,原来这个App对CPU要求这么的高.
.ヾ (o ° ω ° O ) ノ゙

看看使用新方法的情况如何:

1
2
3
4
5
6
7
8
autoreleasepool {
UIGraphicsBeginImageContextWithOptions((self.captureView!.bounds.size),true, 1.0);
let success = self.captureView!.drawViewHierarchyInRect(self.captureView!.bounds, afterScreenUpdates: false)
let img : UIImage = UIGraphicsGetImageFromCurrentImageContext()

UIGraphicsEndImageContext();
cgImg = img.CGImage!;
}


同样是录制了十秒,cpu使用率在90%作用波动,而drawViewHierarchyInRect:方法占用了24.8%的运行时间.
可能是因为被渲染的view没有太复杂的结构,所以两个方法的效果差别不是很大.

内存问题

在调用新方法时,我使用了autoreleasepool.原因是在如果不把这段代码放autoreleasepool中,录制时占用的内存会疯涨.观察代码,在每次调用此方法的时候,都需要新建一个context,在调用完这个方法后,虽然调用了UIGraphicsEndImageContext(),但是由于这段代码外部是一个循环,所以没用的context没有被系统及时释放,而是需要等到录制完成,循环结束的时候才会释放.把这段代码放入autoreleasepool,可以强制每次截图完毕后立刻释放context所占用的空间.

发现新问题

注意到,在上面的图片中,getTimeOfDay方法的占用的cpu时间占到了百分之32,而这个方法的作用是计算时间,当上一帧录制的时间到现在的时间超过1/25秒的时候调用截屏方法.这个方法要一直循环调用,并进行大量的计算.有没有其他方法呢.注意到CADisplayLink可以在屏幕每刷新一次的时候调用指定方法,一般为每秒60次,利用标记变量,很容易使得每秒30次调用截屏方法.30帧和25帧相差不大,事不宜迟,试试新方法.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//开始录制的时候调用-------------------
self.displayLink = CADisplayLink(target: self, selector: #selector(BaseRecorder.cps))
self.displayLink?.addToRunLoop(NSRunLoop.currentRunLoop(), forMode: NSRunLoopCommonModes)
//-----------------------------------

func cps(){
let targetFPS = (CommonDefine.getRecorderFps())
captureFlag += 1
captureFlag %= 2
if(captureFlag == 0){
frameNumber += 1
var presentTime : CMTime = CMTimeMake(Int64(frameNumber), Int32(targetFPS))
self.captureShot(presentTime)
}
}


在利用了CADisPlayLink之后,cpu占用率直接从90%掉到了不足50%,性能上的提高是巨大的.原来,这个小小的计算时间的方法才是拖累性能的关键啊!Time Profiler真是个好东西,帮我找出了拖累性能的罪魁祸首.

CADisplayLink和NSTimer

如果使用NSTimer并且设置60赫兹的频率,CADisplayLink和NSTimer的不同之处是CADisplayLink精确在每一帧展示时调用,而NSTimer的调用位置取决于创建NSTimer时的时间,但是只要不掉帧,其实都可以保证以每两帧为间隔取得后一帧的截图.所以其实使用CADisplayLink和NSTimer的区别是不大的.

CATALOG
  1. 1. 试水
  2. 2. 内存问题
  • 发现新问题
  • CADisplayLink和NSTimer