美文网首页
iOS 卡顿原理及优化、实战

iOS 卡顿原理及优化、实战

作者: 某非著名程序员 | 来源:发表于2020-08-25 21:05 被阅读0次

图像显示原理

  1. 关于CPU和GPU两个硬件都是通过事件总线连接起来的。
  2. 在CPU中输出的结果是一个位图。
  3. 再经由总线在合适的时机上传给GPU,GPU拿到位图之后会做相应位图的图层的渲染,包括纹理的合成。
  4. 之后把结果放到针缓冲区当中。
  5. 由视频控制器根据VSync信号在指定时间之前去提取帧缓冲区当中的屏幕显示内容,然后最终显示到手机屏幕上面。

CPU和GPU分别做了哪些事

  1. 创建一个UIView的控件之后,显示部分是由CALayer来负责的,CALayer中有一个contents属性最终要绘制到屏幕上的位图。比如创建的是一个UILabel,contents中最终结果放置的就是UILabel中Hello World文字的位图
  2. 系统会在合适的时机回调一个drawRect:方法,在此基础之上绘制一些想要自定义绘制的内容。绘制好的内容经由CoreAnimation框架。
  3. 提交给GPU部分的OpenGL渲染管线,进行最终的位图渲染,包括纹理的合成,最终显示到屏幕上。

渲染原理

  1. 任务是以总线的方式进行的,CPU负责计算,GPU负责渲染
  2. CPU UI布局、文本计算、绘制;图片编解码;提交位图
  3. GPU负责顶点着色、图元装配、光栅化、片段着色、片段处理

UI卡顿&掉帧原因

一般页面滑动的流畅性是60ps,每一秒钟是60针的画面,16.7ms产生一针的画面。
在规定的16.7ms内,在下一帧Vsync信号到来之前,并没有CPU和GPU完成下一帧画面的合成,于是就会导致卡顿和掉帧。

卡顿的分类及思考

卡顿分为线上、线下;主线程、子线程;偶现与必现。

  1. 线上的卡顿,我们该如何监控。
  2. 偶现的卡顿,该如何排查。
  3. 子线程严格意义上说并不能归类于卡顿,因为不会阻塞UI,但会导致UI展示出现异常。

线上卡顿的监控源码分析

一些第三方提供了卡顿检测的功能,如腾讯的bugly。腾讯也提供了开源代码:监控RunLoop状态。

源码

- (void)beginMonitor {
    //监测卡顿
    if (runLoopObserver) {
        return;
    }
    dispatchSemaphore = dispatch_semaphore_create(0); //Dispatch Semaphore保证同步
    //创建一个观察者
    CFRunLoopObserverContext context = {0,(__bridge void*)self,NULL,NULL};
    runLoopObserver = CFRunLoopObserverCreate(kCFAllocatorDefault,
                                              kCFRunLoopAllActivities,
                                              YES,
                                              0,
                                              &runLoopObserverCallBack,
                                              &context);
    //将观察者添加到主线程runloop的common模式下的观察中
    CFRunLoopAddObserver(CFRunLoopGetMain(), runLoopObserver, kCFRunLoopCommonModes);
    
    dispatch_queue_t serial = dispatch_queue_create("serial", DISPATCH_QUEUE_SERIAL);
    
    //创建子线程监控
    dispatch_async(serial, ^{
        //子线程开启一个持续的loop用来进行监控
        while (YES) {
            long semaphoreWait = dispatch_semaphore_wait(dispatchSemaphore, dispatch_time(DISPATCH_TIME_NOW,  3 * NSEC_PER_SEC));
            if (semaphoreWait != 0) {
                if (!runLoopObserver) {
                    timeoutCount = 0;
                    dispatchSemaphore = 0;
                    runLoopActivity = 0;
                    return;
                }
                //两个runloop的状态,BeforeSources和AfterWaiting这两个状态区间时间能够检测到是否卡顿
                if (runLoopActivity == kCFRunLoopBeforeSources || runLoopActivity == kCFRunLoopAfterWaiting) {
                    //出现三次出结果
//                    if (++timeoutCount < 3) {
//                        continue;
//                    }
                    NSLog(@"monitor trigger");
                    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
                        [SMCallStack callStackWithType:SMCallStackTypeAll];
                    });
                } //end activity
            }// end semaphore wait
            timeoutCount = 0;
        }// end while
    });
}

static void runLoopObserverCallBack(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info){
    SMLagMonitor *lagMonitor = (__bridge SMLagMonitor*)info;
    lagMonitor->runLoopActivity = activity;
    
    dispatch_semaphore_t semaphore = lagMonitor->dispatchSemaphore;
    dispatch_semaphore_signal(semaphore);
}
  1. CFRunLoopAddObserver为主线程添加runloop观察者。
  2. 子线程开启while循环。
  3. 如果主线程没有卡顿, RunLoop的状态会实时发生变化,调用runLoopObserverCallBack中的dispatch_semaphore_signal,while中的 dispatch_semaphore_wait不会超时。
  4. 如果主线程有卡顿,while中的dispatch_semaphore_wait会发生超时。
  5. 监听两个状态kCFRunLoopBeforeSources、kCFRunLoopAfterWaiting。
    timeoutCount如果连续出现3次,认为主线程发生了卡顿。callStackWithType:抓取主线程中的堆栈信息,进行上传。

思考:

  1. 子线程开启while循环,会不会有性能损耗?
    分析:如果主线程卡住了,子线程会挂起,等待信号量超时。不会一直死循环。
    如果主线程没有卡主,主线程的循环跟着runloop的唤醒频率来刷新。
  2. 为什么是kCFRunLoopBeforeSources、kCFRunLoopAfterWaiting两个状态,其他状态行不行?
    首先发生卡顿肯定是主线程的任务发生卡顿,做事情的时候发生卡顿。
    kCFRunLoopBeforeTimers 触发timers回调,任务轻。
    kCFRunLoopBeforeSources触发source0开始干活了,如果发生了卡顿,肯定无法进入下一个状态。
    kCFRunLoopAfterWaiting 接受mach_port消息,如果发生了卡顿,肯定无法进入下一个状态。
  3. 为什么需要timeoutCount,如果不加行吗?

卡顿实战

  1. 如果app发生卡顿,能获抓取到主线程的堆栈信息,基本能解决一半。
  2. 堆栈信息还需要仔细分析,有些情况可能都没见过。

实战1: 使用YYThreadSafeArray造成的页面卡死

背景

在遍历数组时,可能会获取前一个或后一个obj

[conversation.messages enumerateObjectsUsingBlock:^(BLMessage *message, NSUInteger index, BOOL * _Nonnull stop) {
    if (index == 0) {
        ...
    } else {
        BLMessage *compareMessage = [conversation.messages objectAtIndex:index - 1];
        ...
}];

原因

#define LOCK(...) dispatch_semaphore_wait(_lock, DISPATCH_TIME_FOREVER); \
__VA_ARGS__; \
dispatch_semaphore_signal(_lock);

- (void)enumerateObjectsUsingBlock:(void (^)(id obj, NSUInteger idx, BOOL *stop))block {
    LOCK([_arr enumerateObjectsUsingBlock:block]);
}

- (id)objectAtIndex:(NSUInteger)index {
    LOCK(id obj = [_arr objectAtIndex:index]); return obj;
}

YYThreadSafeArray使用信号量来保证线程安全; enumerateObjectsUsingBlock遍历时,信号量已经dispatch_semaphore_wait,而再调用objectAtIndex会再次调用dispatch_semaphore_wait,造成死锁。

解决方案

  1. 如果使用for循环进行遍历,不会存在死锁,但非线程安全。会造成偶现闪退。
  2. 自定义信号量,在数组操作时进行加信号量,注意方法中不能出现嵌套。

小提示

使用YYThreadSafeArray正常遍历遍历是没有问题的,但注意不能再进行其他数组操作。

实战2. socket快速发送消息会出现接受消息阻塞

场景:快速点击某个按钮,发送消息时,会出现socket响应消息丢失。

  1. 最后分析出现是-[NSInputStream read:maxLength:]卡主了。
  2. 下面是出现的解释:TCP Socket的一些行为Does -[NSInputStream read:maxLength:] block?
  3. read:maxLength:会阻塞,直到至少有一个字节可用之后,或者发生错误或者流到达EOS。它也将阻塞,直到打开流为止。

解决方案

  1. 通过心跳来打破这种等待行为
  2. 使用超时的定时器,每秒触发一次,当read:maxLength阻塞时发送心跳。

实战3. 加密引起的界面卡顿问题沟通

  1. 加入视频会议,有时候会特别慢。也不是必现。
  2. 堆栈信息跟踪到声网的[self.agoraKit enableEncryption:YES encryptionConfig:config]这段加密方法卡住了。最慢能有8s。
    反馈到声网,声网一度怀疑是我们调用SDK逻辑有问题。然后发了个demo,给我测试。
  3. 测试发现,快速加入退出会议,demo最慢也会卡2s。
  4. 分析问题:声网的AgoraRtcEngineKit是单例,加密对象只调用了config设置。
    而我们音视频加密是一个会议一个秘钥,也就是退出会议,秘钥会进行释放。声网内部的资源竞争导致的耗时。
    再次分析发现:有些方法不能在加密之前调用,如本地视频流的推送。个人理解在推视频流,然后又加密了,这个时候SDK内部会对推流视频做加密处理,导致卡顿。

总结

  1. 对于一些偶现的卡顿,可以观察线上统计的数据,如bugly平台。
  2. 卡顿问题也是千奇百怪。了解卡顿检测的原理。
  3. 你会遇到哪些有意思的卡顿问题,欢迎留言交流。

相关文章

  • iOS 卡顿原理及优化、实战

    背景:conversation.messages是YYThreadSafeArray;下面遍历会造成死锁: 原因:...

  • iOS - 界面优化

    APP的界面优化 什么样的界面让你觉得需要被优化呢?就是界面会卡顿咯。 下面介绍 卡顿原理 卡顿检测 实战 1、界...

  • 无标题文章

    APP性能优化 UI卡顿优化 View的绘制原理 UI卡顿原理分析 UI卡顿检测分析 BlockCanary原理分...

  • iOS 卡顿原理及优化笔记

    一、CPU和GPU的工作 相当于CPU跟GPU提出需求,我需要一个长宽高为50的箱子,并且希望在这个箱子上面刻有x...

  • iOS卡顿优化

    iOS卡顿优化

  • Activity 性能优化方案

    Activity 性能优化方案UI 卡顿原理UI卡顿常见原因优化手段 UI 卡顿原理人类大脑与眼睛对一个画面的连贯...

  • 性能优化

    面试题 CPU和GPU 屏幕成像原理 卡顿产生的原因 卡顿优化 - CPU 卡顿优化 - GPU 离屏渲染 卡顿检...

  • iOS 性能优化

    iOS的性能优化主要可提现在以前的几个方面:卡顿优化、耗电优化、启动优化、安装包的瘦身。 1、卡顿优化 在了解卡顿...

  • iOS必读 - 收藏集 - 掘金

    iOS 性能优化总结 - iOS - 掘金关于iOS 性能优化梳理: 基本工具、业务优化、内存优化、卡顿优化、布局...

  • 性能优化:屏幕卡顿优化

    一、屏幕成像原理及屏幕卡顿原因二、屏幕卡顿优化三、定量监测屏幕FPS四、定位卡顿效果五、定位耗时代码六、果然好客服...

网友评论

      本文标题:iOS 卡顿原理及优化、实战

      本文链接:https://www.haomeiwen.com/subject/vuezjktx.html