第二章:性能估计——Measuring Performance

在代码优化之前需要找到代码中存在的问题以及造成这种问题的根本原因。但是不要陷入过度优化的陷阱。本章我们会介绍几个高性能评估的几个关键参数。

参数及测量

为了提高性能,我们必须首先找出我们所要评估的问题。如果没有可以提升的话,那么也就没有什么可以优化的问题。

以应用为基础——该应用是用来做什么以及怎么做,这些参数中的一个或者多个能够适用于该应用。

内存

说道内存,所指的是应用所耗费的RAM。应用有三个部分的内存消耗。

堆栈的大小

应用的新线程的堆栈控件大小包括预留空间以及耗费的内存。当退出线程的时候会放空堆空间。线程的最大堆空间大小是很有限的:

  • 可以递归调用的方法数量上限。每个方法都有自己的堆栈帧,构成消耗的堆栈空间。所以,如果调用顺序是main 函数——方法1——方法2,那么这三个方法都会占用空间。图1 所表示的是线程堆栈随时间的变化。
  • 方法中使用的变量的个数。所有的变量都从方法的堆栈上进行加载。
  • 层级中view的数量。view的渲染会调用layoutSubViews以及drawRect方法。如果层次很深的话比较容易造成堆栈溢出现象。
堆栈随时间.png

堆大小——Heap size

一个进程的所有线程享有同一个堆。一个应用可用的空间比设备的RAM要小很多。例如,iPhone 5S有1GB的RAM,但是每个应用至多可以有512M的内存甚至更少。

使用NSString、加载图片、创建或者解析JSON/XML数据以及view的使用都会产生堆空间的消耗。如果你的应用侧重图片的话——也就是说你的应用是一个图片处理类应用——你需要更加注重内存的消耗。

图2 展示了在应用中会存在的一种典型的堆

heap.png

对象的寿命和泄露——Object longevity and leaks

对象在内存中存在的时间越长,对象所产生的内存不被清除的可能性就越高,所以要尽量避免让对象长久的存在在内存中。所以需要细心检查关键代码,因为你也不想每次都重新再次创建同一个对象。

应用中存在内存中最长时间的应该是单例对象,他们只创建一次却从不被销毁。

另一种就是全局变量。在高级编程语言当中,全局变量是最可怕的。

随着对象图变得越来越复杂,内存被清理的机会越来越渺茫,最可能产生的后果就是越来越频繁的内存崩溃。如果一个APP中有多条类似出具和网络操作的线程,并且他们需要同步执行的话,那么主线程只能等待其它地方所触发的线程执行完毕了才能响应用户的其它操作。

响应时间

表示的是对于用户操作的响应时间。例如,在用户点击了刷新按钮之后,APP发送请求,用一些动画来表示请求进度的这个时间应该尽量缩短。

执行时间

一个关键的评估指标,代码中的关键算法的执行时间。多次执行和评估能够有助于更好的了解代码的有效性以及数据结构是否使用的比较好。

网络

只要你的应用中涉及到了网络,也需要评估网络的使用。检测当一则广告出现时候的极端情况下网络的使用情况。一般的需要检测:

  • 传输的字节数
  • 平均以及最大带宽

耗电量

任何硬件功能都会消耗电池电量。消耗电量的主要活动有:

  • 密集的任务,如图片处理或者滚动
  • 使用像照相机、蓝牙以及话筒等硬件
  • 通过推送通知唤醒应用
  • 后台抓取

磁盘存储

评估应用所消耗的磁盘存储空间:

  • 应用的二进制大小
  • 文件的创建或者存储
  • 持续的缓存
  • 本地数据库存储

崩溃

这个是对应用性能估计的一个重要因素。不管因为什么原因导致了最终的崩溃,一旦应用崩溃了就说明性能有待优化。评估崩溃主要从发生崩溃的原因、类型以及频次来考虑。

崩溃报告

崩溃报告系统可以使我们及时知道崩溃的发生。所有的报告系统都会从服务器加载崩溃日志。以帮助分析崩溃产生的原因以及如何修正错误。

有许多可用的报告系统,而本文采用的是Flurry。而原因就是我可以像instrumentation一样,在即使只使用一个SDK的情况下设置崩溃报告。

可以在Flurry的官网上注册一个账号然后获得一个API key,并且下载该SDK。

#import "Flurry.h"
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
  [Flurry setCrashReportingEnabled:YES];
  [Flurry startSession:@"API_KEY"];
}

在这里,可以将代码中的API_KEY换成自己的项目的。

Instrumentation

Instrumentation不仅仅在了解用户行为方面,而且在辨认应用的关键路径方面都具有重要作用。有意注入代码来标注关键指标是改善应用性能的关键一步。

在应用的生命周期的四个部分都会使用Instrumentation

  1. 在应用到前台,也就是获得焦点的时候 applicationDidBecomeActive:
  2. 当应用处于后台applicationDidEnterBackground:的时候
  3. 在应用受到内存警告的时候applicationDidReceiveMemoryWarning:
  4. 仅仅是为了有趣,我们在HPVFirstViewController添加一个按钮,并且在点击这个按钮的时候会让应用崩溃。
- (void)applicationDidBecomeActive:(UIApplication *)application {
    [Flurry logEvent:@"App_Activate"];
}
- (void)applicationDidEnterBackground:(UIApplication *)application {
    [Flurry logEvent:@"App_Background"];
}
- (void)applicationDidReceiveMemoryWarning:(UIApplication *)application {
    [Flurry logEvent:@"App_MemWarn"];
}

图3表示的是添加一个按钮

崩溃按钮.png

HPVFirstViewController.m代码

- (IBAction)crashButtonWasClicked:(id)sender {
    [NSException raise:@"Crash Button was Clicked" format:@""];
}

现在让我们多操作几次应用的启动、后台、前台并且点击后来添加的那个崩溃按钮
再次启动该应用,这时候就会从服务器返回崩溃报告了。

图4 展示的是用户行为报告

Report of user sessions.png

图5 是更加重要的应用的事件报告

Events - more important report.png

图6 是最重要的崩溃报告

Crash Report - most important report.png

点击途中红圈标注的Download按钮,可以下载崩溃日志。

日志

日志是可以知道应用所发生的操作的非常重要的工具,严格的说来,它与instrumentation还是有一些细微的差别的,这些差别主要体现在:

  • 日志是本地的,但是instrumentation是实时的将数据传递到服务器进行分析
  • instrumentation通常都被谨慎而少量的使用,而日志则通常被密集的使用。因为instrumentation发送大量的设备数据会消耗一些带宽。
  • 日志通常会有详细的、调试、警告以及错误信息等,但是在instrumentation会先找到最佳的优化点然后再继续。

我建议instrumentation辅助日志记录,因为我认为,只要是能被instrumentation的也可以用日志达到。

现在将用NSLog来输出日志并且向服务器传递细节。

//HPInstrumentation.h
@interface HPInstrumentation : NSObject +(void)logEvent:(NSString *)name;
+(void)logEvent:(NSString *)name withParams:(NSDictionary *)params;
@end
//HPInstrumentation.m
@implementation HPInstrumentation
+(void)logEvent:(NSString *)name {
    NSLog(@"%@", name);
        [Flurry logEvent:name];
}
+(void)logEvent:(NSString *)name withParams:(NSDictionary *)params {
        NSLog(@"%@ -> %@", name, params);
        [Flurry logEvent:name withParameters:params];
}
@end

此外,我们还通过HPLogger来输出日志。首先,它仅仅是NSLog来输出日志。以后,我们可以使日志输出到文件或者是内存当中并且完整的展示到应用界面。日志级别可以在应用启动的时候只设置一次。
HPLogger类的代码如下所示:

//HPLogger.m
@interface HPLogger : NSObject +(int) logLevel;
+(void) setLogLevel:(int) level;
+(void)v:(NSString *) message; +(void)d:(NSString *) message; +(void)i:(NSString *) message; +(void)w:(NSString *) message; +(void)e:(NSString *) message;
@end

//HPLogger.m
static const int LEVEL_VERBOSE = 1; static const int LEVEL_DEBUG = 2; static const int LEVEL_INFO = 3; static const int LEVEL_WARNING = 4; static const int LEVEL_ERROR = 5;
@implementation HPLogger
static int _level = LEVEL_VERBOSE;
+(int) logLevel {
@synchronized(self) { return _level;
} }
+(void) setLogLevel:(int) level {
@synchronized(self) { _level = level;
} }
+(void)v:(NSString *) message {
if(_level <= LEVEL_VERBOSE) { NSLog(@"[V] %@", message);
} }
+(void)d:(NSString *) message {
if(_level <= LEVEL_DEBUG) { NSLog(@"[D] %@", message);
} }
+(void)i:(NSString *) message {
if(_level <= LEVEL_INFO) { NSLog(@"[I] %@", message);
} }
+(void)w:(NSString *) message {
if(_level <= LEVEL_WARNING) { NSLog(@"[W] %@", message);
} }
 
+(void)e:(NSString *) message {
if(_level <= LEVEL_ERROR) { NSLog(@"[E] %@", message);
} }
@end

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,372评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,368评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,415评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,157评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,171评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,125评论 1 297
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,028评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,887评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,310评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,533评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,690评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,411评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,004评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,659评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,812评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,693评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,577评论 2 353

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 172,071评论 25 707
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,651评论 18 139
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,622评论 18 399
  • 本文选译自《Threading Programming Guide》。 导语 线程技术作为在单个应用程序中并发执行...
    巧巧的二表哥阅读 2,433评论 4 24
  • 两年前写,今天拿出来再看看,有不一样的心情。 当你被姑娘来一句:你是一个好人的时候。有时候,自己都不禁阿Q一下对自...
    阿东咚咚咚阅读 615评论 1 1