开发应用

六、 性能优化部分点的分析

2022-05-10  本文已影响0人  流小贝

iOS 性能优化部分点的分析

一、时间复杂度

1、在集合里数据量小的情况下时间复杂度对于性能的影响看起来微乎其微。但如果某个开发的功能是一个公共功能,无法预料调用者传入数据的量时,这个复杂度的优化显得非常重要了。

上图列出了各种情况的时间复杂度,比如高效的序算法一般都是 O(n log n)。接下来看看下图:

图中可以看出 O(n) 是个分水岭,大于它对于性能就具有很大的潜在影响,如果是个公共的接口一定要加上说明,自己调用也要做到心中有数。当然最好是通过算法优化或者使用合适的系统接口方法,权衡内存消耗争取通过空间来换取时间。

2、下面通过集合里是否有某个值来举个例子:

//O(1)
return array[idx] == value;

//O(n)
for (int i = 0; i < count; i++) {
    if (array[i] == value) {
        return YES;
    }
}
return NO;

//O(n2)找重复值
for (int i = 0;  < count; i++) {
    for (int j = 0; j < count; j++) {
        if (i != j && array[i] == array[j]) {
            return YES;
        }
    }
}
return NO;

3、那么 OC 里几种常用集合对象提供的接口方法时间复杂度是怎么样的。

1、NSArray / NSMutableArray

首先我们发现他们是有排序,并允许重复元素存在的,那么这么设计就表明了集合存储没法使用里面的元素做 hash table 的 key 进行相关的快速操作。所以不同功能接口方法性能是会有很大的差异。

2、NSSet / NSMutableSet / NSCountedSet / NSOrderedSet / NSMutableOrderedSet

这些集合类型是无序没有重复元素。这样就可以通过 hash table 进行快速的操作。比如 addObject:, removeObject:, containsObject: 都是按照 O(1) 来的。需要注意的是将数组转成 Set 时会将重复元素合成一个,同时失去排序。

3、NSDictionary / NSMutableDictionary

和 Set 差不多,多了键值对应。添加删除和查找都是 O(1) 的。需要注意的是 Keys 必须是符合 NSCopying。

4、containsObject 方法在数组和 Set 里不同的实现

在数组中的实现

- (BOOL) containsObject: (id)anObject
{
  return ([self indexOfObject: anObject] != NSNotFound);
}
- (NSUInteger) indexOfObject: (id)anObject
{
  unsigned  c = [self count];

  if (c > 0 && anObject != nil)
  {
      unsigned  i;
      IMP   get = [self methodForSelector: oaiSel];
      BOOL  (*eq)(id, SEL, id)
    = (BOOL (*)(id, SEL, id))[anObject methodForSelector: eqSel];

     for (i = 0; i < c; i++)
    if ((*eq)(anObject, eqSel, (*get)(self, oaiSel, i)) == YES)
      return i;
    }
  return NSNotFound;
}

可以看到会遍历所有元素在查找到后才进行返回。

接下来可以看看 containsObject 在 Set 里的实现:

- (BOOL) containsObject: (id)anObject
{
  return (([self member: anObject]) ? YES : NO);
}
//在 GSSet,m 里有对 member 的实现
- (id) member: (id)anObject
{
  if (anObject != nil)
    {
      GSIMapNode node = GSIMapNodeForKey(&map, (GSIMapKey)anObject);
      if (node != 0)
    {
      return node->key.obj;
    }
    }
  return nil;
}

找元素时是通过键值方式从 map 映射表里取出,因为 Set 里元素是唯一的,所以可以 hash 元素对象作为 key 达到快速获取值的目的。hash概念可以参考这篇文章了解一下: 哈希表、时间复杂度、链表

通过测试可以发现:

二、I/O 性能优化

1、I/O 是性能消耗大户,任何的 I/O 操作都会使低功耗状态被打破,所以减少 I/O 次数是这个性能优化的关键点,为了达成这个目下面列出一些方法。

2、NSCache 具有字典的所有功能,同时还有如下的特性:

三、用 GCD 来做优化

1、我们可以通过 GCD 提供的方法来将一些需要耗时操作放到非主线程上做,使得 App 能够运行的更加流畅响应更快。但是使用 GCD 时需要注意避免可能引起线程爆炸和死锁的情况,还有非主线程处理任务也不是万能的,如果一个处理需要消耗大量内存或者大量CPU操作 GCD 也没法帮你,只能通过将处理进行拆解分步骤分时间进行处理才比较妥当。

 1、异步处理事件
上图是最典型的异步处理事件的方法。

2、需要耗时长的任务
将 GCD 的 block 通过 dispatch_block_create_with_qos_class 方法指定队列的 QoS 为 QOS_CLASS_UTILITY。
这种 QoS 系统会针对大的计算,I/O,网络以及复杂数据处理做电量优化。

3、避免线程爆炸

举个例子,下面的写法就比较危险,可能会造成线程爆炸和死锁

for (int i = 0; i < 999; i++) {
dispatch_async(q, ^{...});
}
dispatch_barrier_sync(q, ^{});

那么怎么能够避免呢?首先可以使用 dispatch_apply

dispatch_apply(999, q, ^(size_t i){...});

或者使用 dispatch_semaphore

#define CONCURRENT_TASKS 4
sema = dispatch_semaphore_create(CONCURRENT_TASKS);
for (int i = 0; i < 999; i++){
    dispatch_async(q, ^{
        dispatch_semaphore_signal(sema);
    });
    dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
}

四、界面性能优化

1、产生卡顿的原因

2、CPU 资源消耗原因和解决方案

1、对象创建

2、对象调整

3、布局计算

4、文本计算

5、文本渲染

6、图片解码

7、图片绘制

3、CPU 资源消耗原因和解决方案

GPU 能干的事情比较单一:接收提交的纹理(Texture)和顶点描述(三角形),应用变换(transform)、混合并渲染,然后输出到屏幕上。

1、纹理的渲染

2、视图的混合

3、图形的生成

性能优化参考文章

<font color=#FF0000 >
1、<font color=#FF0000 >参考文章1</font>

2、<font color=#FF0000 >参考文章2</font>
</font>


上一篇 下一篇

猜你喜欢

热点阅读