月度归档:2015年02月

iOS 性能优化:Instruments 工具的救命三招

对于每位 iOS 开发者来说,代码性能是个避不开的话题。随着项目的扩大和功能的增多,没经过认真调试和优化的代码,要么任性地卡顿运行,要么低调地崩溃了之……结果呢,大家用着不高兴,开发者也不开心。

其实要破这个局面并不难,只要在 Xcode 自带的监控调试工具 Instruments 上花点功夫,让大代码流畅运行也不是神话。Instruments 提供了很多功能,我会重点介绍一下我最常用的三大类:

  • Time Profiler:分析代码的执行时间,找出导致程序变慢的原因。
  • Allocations:监测内存使用/分配情况
    迅速膨胀的内存可以很快让程序毙命,所以要多加防范。
  • Leaks:找到引发内存泄漏的起点

即使有 ARC(自动引用计数)内存管理机制,但在现实中对象之间引用复杂,循环引用导致的内存泄漏仍然难以避免,所以关键时刻还要自力更生。

针对这三方面的测试,我写了个演示应用,放在 GitHub 上,来帮助大家更直观地了解这些工具的使用方法。好,进入正题。
001

Time Profiler

时间都去哪儿啦? Time Profiler 可以回答。它会按照设定的时间间隔(默认 1 毫秒)来跟踪每一线程的堆栈信息(stack trace),并通过比较时间间隔之间的堆栈状态,来推算出某个方法执行了多久,给出一个近似值。
在演示应用头一项「Time Profiler: System Methods」中,我用插入排序(Insertion Sort)和冒泡排序(Bubble Sort)两种算法来做性能比较,下面是 Swift 代码:

/* 引用自:http://waynewbishop.com/swift/sorting-algorithms/ */

func insertionSort() {
    var x, y, key: Int

    for (x = 0; x < numberList.count; x++) {
        key = numberList[x]

        for (y = x; y > -1; y--) {
            if key < numberList[y] {
                numberList.removeAtIndex(y + 1)
                numberList.insert(key, atIndex: y)
            }
        }
    }
}

func bubbleSort() {
    var x, y, z, passes, key : Int

    for (x = 0; x < numberList.count; ++x) {
        passes = (numberList.count - 1) - x;

        for (y = 0; y < passes; y++) {
            key = numberList[y]

            if (key > numberList[y + 1]) {
                z = numberList[y + 1]
             numberList[y + 1] = key
                numberList[y] = z
            }
        }
    }
}

这段代码主要是对数组的添加和删除,两种方法执行起来耗时不多,但后台发生的系统动作却多得让人眼晕。
004
可以发现,代码用到了很多间接依赖,这些都是支撑代码运行的系统库文件。因为处理大数据集比较消耗系统资源,所以要尽可能地把繁重的操作放到后台去做,上面的代码就走的后台线程。在上图的 Call Tree 中可以看到,被调用的堆栈名是 dispatch_worker_thread3。如果把它放到主线程去执行,程序肯定会挂起。不信你注释掉 dispatch_async 调用看一下。
再来个图片加载的例子。
003
这儿有三种图片加载方法:

  • loadSlowImage1:从指定 URL 下载一张图片(加载速度慢)
  • loadImage2:从本地资源库加载一张图片(注意:没用系统缓存)
  • loadFastImage3:从系统缓存中加载一张图片(加载速度快)

我们来看看 Time Profiler 算出的结果是不是跟预想的一样。

进入演示应用第二项「Time Profiler: Our Methods」,点击「Reload」十次来重复加载图片,这样能产生足够的数据来分析。然后在 Time Profiler 图表中通过拖拉鼠标选中要放大查看的区域,从 Call Tree 中双击调用了 .reload 方法那一行(上图中加亮选中那一行),就会跳转到对应的代码行,所用时间也标注出来了。
004
看到谁最花时间了吧。虽然代码没什么可优化的地方,但大家应该认识到缓存能发挥的作用。所以即使有时还得调用 loadSlowImage,多数情况下把图片缓存下来,还是能省些资源占用。

此外,我想再说说 Call Tree 的选项设置。
005
这些选项默认是不选的,但把它们勾选上可以帮你更快定位到关键的代码上,往往这也是问题的源头。

  • Separate by Thread:按线程分开做分析,这样更容易揪出那些吃资源的问题线程。特别是对于主线程,它要处理和渲染所有的接口数据,一旦受到阻塞,程序必然卡顿或停止响应。
  • Invert Call Tree:反向输出调用树。把调用层级最深的方法显示在最上面,更容易找到最耗时的操作。
  • Hide Missing Symbols:隐藏缺失符号。如果 dSYM 文件或其他系统架构缺失,列表中会出现很多奇怪的十六进制的数值,用此选项把这些干扰元素屏蔽掉,让列表回归清爽。
  • Hide System Libraries:隐藏系统库文件。过滤掉各种系统调用,只显示自己的代码调用。
  • Flattern Recursion:拼合递归。将同一递归函数产生的多条堆栈(因为递归函数会调用自己)合并为一条。
  • Top Functions:找到最耗时的函数或方法。

需要添加其他工具的话:
007

Allocations

我们经常需要从服务器下载大量图片,特别是开发照片类的应用。但往往稍不注意,内存使用就会暴增,所以得保证把这些图片缓存下来以便重复使用。下面来看看演示程序中内存分配的例子。
008
从图中可以看到,每次点击「Reload」重新载入图片时,内存都会出现使用峰值。应用先分配大量内存来替换原有图片,然后再释放掉这部分内存,可想而知这样的操作效率高不了,而且如果要下载更大的文件,呃,局面大概会失控吧。

看一下堆栈列表第四行,ImageIO_PNG_Data 里有 9 张处于活动状态的图片,占用了 12.38 MB 内存,这些都是没被系统释放或缓存的内存,所以导致堆内存分配升高。接下来再看看使用缓存后的效果。
009
使用了缓存库(Swift Haneke)后,点「Reload」五次,这回在 Allocations 列表中却看不到 ImageIO_PNG_Data 对象了,这说明它是空的,没有任何图像数据。同时,All Heap Allocations 的大小已从刚才的 14.61 MB 降到了 2.51 MB。Anonymous VM(匿名虚拟内存)是系统为程序预留的、可能会立即被重复使用的一部分可用内存。要防止程序崩溃,就别让堆的尺寸增长太快。

还有就是,例子用的是异步方式来加载图片,这样用不着等到所有图片下载完才能在界面中显示。大多数图像缓存库都会把加载工作放到后台,以避免延长主线程的响应周期。

Leaks

尽管 Apple 推出的 ARC 可以有效防范内存泄漏,但出问题的机率还是会有,Swift 也不例外。鉴于篇幅有限,本文就不涉及内存和 ARC 的工作原理了,具体可以参考 官方文档 。我会用代码来触发内存泄漏。
首先从最底层上说,当两个对象相互建立了强引用(strong reference),当一个对象被释放,另一个对象由于是强引用的关系不允许被释放,此时 ARC 无法确定没被释放的对象到底还有没有用,于是就导致了内存泄漏。
010
要解决这个问题,可以将其中的一个对象中变量设为 weak,不让它出现在保留周期中。很多开发者在管理 view controller 时常会在内存泄漏上中招,以为换了新的 controller,老的 controller 就被释放回收了,其实还没。这样代码一多,就会造成很多对象都没被释放。所以用这个工具把整个应用跑一遍,把那些断链的强引用清理干净,会大有裨益。

除了上述这三类工具,Instruments 还有很多实用的工具,推荐大家根据自己的关注点,花些时间去学学。比如:

  • Core Data:监测读取、缓存未命中、保存等操作,能直观显示是否保存次数远超实际需要。
  • Cocoa Layout:观察约束变化,找出布局代码的问题所在。
  • Network:跟踪 TCP / IP 和 UDP / IP 连接。
  • Automations:创建和编辑测试脚本来自动化 iOS 应用的用户界面测试。

最后小总结下。我倒不想一味夸大 Instruments 的作用,如果应用跑得挺痛快,没出现啥调皮行为,大可把它忽略,等到问题来了再做优化。对于新手来说,花些时间了解 Instruments 的功能,多调试多积累经验,这样做出来的应用在用户体验上肯定错不了。

你最常用的 Instruments 工具都有哪些?欢迎与我们分享。

原文:How To Use The 3 Instruments You Should Be Using
引言:
你的 iOS 应用,运行速度靠谱吗?中枪的同学莫要愁,性能优化咱有妙招。用 Xcode 自家的调试工具 Instruments,揪出那些堵线程、占内存、耗资源的问题代码,彻底破掉迷局,让应用扬眉吐气喽~~

晨读推荐 #008

晨读推荐

关于「晨读推荐」

由 LeanCloud 的工程师和设计师,以周为单位进行阅读分享,包含每位工程师和设计师想加入自己读到的「有价值、有趣味、有分享意义」的文章以及推荐理由。每周一和周三在微信公众帐号进行推送。

技术向 Geek Must Read

《互联网全站 HTTPS 的时代已经到来》
LeanCloud 导读:
一篇关于 HTTPS 的科普文,介绍了 HTTPS 的意义,如何保证安全性,以及对性能的影响等。从中你也能了解到一些关于对称加密和非对称加密和证书相关的知识。当下我们已经进入到一个需要互联网安全和私密性的时代和环境,自己的网站使用 HTTPS 是对用户的负责。当然,也有很多不负责的行为,但很多人都没有在意过(比如 12306 有自己的根证书,这意味着什么?)。

工具向 The best tools

《11 个你不造的 Python 小库库》
LeanCloud 导读:
这里面介绍的主要是 11 个非常赞的 Python 库,功能简单但是强大。适合做命令行小工具的 Python 库,处理的都是平时比较麻烦的场景,可以加入收藏做积累。除了好用以外,茶余饭后也可以读一下他们的源码,也许会不错。

设计向 Design More,Design Better

《Progressive Enhancement Data Visualizations》
LeanCloud 导读:
本文以逐渐增强演变的方式,讲述当需要展示一个数据报表的几种方式。 从最基本的 dl dt HTML 基本标签方式,到运用简单的 css 实现一些增强效果。到借助 JS 画图,可能平时没注意到这些实现方式,但这些方式看上去非常自然。

新知向 Ren chou jiu yao duo du shu

《Holding A Program In One’s Head》
LeanCloud 导读:
Paul Graham 写过《Hackers And Painters》,《ANSI Common Lisp》,《On Lisp》,在编程上有自己的一些经验。数学家解决问题的时候,更多是把问题充分理解,载入大脑中,在其中自由漫步,就像在小时候住的房子里漫步一样。程序也是一样。作者给出一些建议,来让程序整个载入大脑中。比如其中的一条,不要有很多人编辑同一份代码。读了他人的代码,也仅仅是读了,而没有写。所以可能没有考虑到隐含的例外情况,造成 bug。应该明确划分独立的模块,不同的人负责不同的代码,并在两份代码的衔接处充分测试。实际中,我也观察到,有些团队为了赶进度,很多人来写同一部分的代码,只能每天加班来修 bug 了。

晨读推荐 #007

晨读推荐4

关于「晨读推荐」

由 LeanCloud 的工程师和设计师,以周为单位进行阅读分享,包含每位工程师和设计师想加入自己读到的「有价值、有趣味、有分享意义」的文章以及推荐理由。每周一和周三在微信公众帐号进行推送。

技术向 Geek Must Read

《Using FlexBox today》
LeanCloud 导读:
Flexbox 做现代 UI 比较简单,本文介绍了 Flexbox 可以实现的设计效果,包含一个互动的效果查看 demo,而且这个 demo 很方便理解其中的属性的含义。

工具向 The best tools

JavaScript Promise 迷你书
LeanCloud 导读:
一直以来,不管是在浏览器环境还是 node.js 环境中,JavaScript 的「回调地狱」一直是使用者的一大痛处。Promise 是解决这个问题的一个比较好的方案。在已经正式发布的 ES6 中 JavaScript 已经内置了原生的 Promise 对象,配合未来 ES7 中的 async/await 关键字,我们有望完美解决这个问题。本文就以此为主题,介绍了什么是 Promise 以及 JavaScript 中 Promise 解决了哪些问题。

设计向 Design More,Design Better

《设计师的春天:中文 WebFont 解决方案 Font-Spider(字蛛)》
LeanCloud 导读:
WebFont 技术提供了在网页使用特殊字体的可能,从而避免用图片的方法。它的实现方法是通过 CSS 的 @font-face 引入字体。很多互联网公司已经率先采用了这种方法,比如 Apple 官网就是采用了自己的字体。Google 也推出了免费的 WebFont 云托管服务,在国外网站自定义字体得到很好的应用。
Font-Spider(字蛛)是一个 web 字体工具,通过自动化技术来压缩、转码跨平台的中文字体,让网页自由嵌入中文字体成为可能。作为业界首款中文 WebFont 本地自动化压缩与转码工具,中文字体 web 化问题迎刃而解。

新知向 Ren chou jiu yao duo du shu

《什么是浏览器指纹?》
LeanCloud 导读:
这是一篇科普文章,介绍了「浏览器指纹」的概念。服务器端可以通过一些方式,来识别出用户,不知不觉间你已经泄露了你的隐私。

LeanCloud 春节放假通知

LeanCloud 于 2015 年春节期间(2.18 日-2.24 日)按国家法定假日规定进行休假,2 月 25 日恢复正常工作。 假日期间,LeanCloud 工程师会及时处理紧急事件确保服务的稳定,您仍可以通过工单系统来提交您的问题,但同时希望用户能体谅在节假日期间,工程师并不能保证及时相应,如有遗漏我们会在节后第一时间进行处理。紧急情况请联系值班人员电话:13011098244,我们将及时处理。

2015 年 2 月上旬更新汇总

在春节来临之间,LeanCloud 祝福所有开发者羊年大吉,阖家幸福。

2 月份到今天为止,我们为 LeanCloud 增加了不少更新,汇总如下。

网站和控制台

  • API 统计增加云代码平台调用统计。
  • 云代码在线编辑增加 AV.Cloud.onLogin 函数,支持在用户登录前执行检查操作。
  • 离线数据分析增加导出和保存为 class 功能,您可以导出查询分析结果,或者将查询结果保存为 Class 数据以供增删改查。
  • 离线数据分析的查询增加耗时提示。

存储和离线分析服务

  • 修复 _Follower_Followee 在取消关注的时候没有触发云代码 afterDelete/beforeDelete 的 Bug
  • 用户反馈组件支持推送消息给用户。
  • 修复删除列没有同时删除索引的 bug
  • 离线分析增加对应用间共享 class 的支持
  • 离线分析支持结果数据导出和另存为 class 功能。

聊天服务

  • 增加获取群组在线人数 REST API。
  • 预告:我们即将推出全新设计的聊天 2.0 SDK,新 SDK 将更易于使用,效率更好,敬请期待。

云代码服务

  • 云代码因为压力和容量问题,上旬遇到了故障,请查看 《02-10~02-11 云代码故障说明》,对此我们表示万分抱歉。
  • 集中精力提升云代码服务的稳定性,特别是部署和运行。
  • 系统针对春节流量的扩容运维等。
  • 云代码命令行工具 发布 0.6.5,增加 onLogin hook 支持、增加 lint 命令、兼容 io.js、修复一些 Bug 等,建议升级。

推送服务

  • 改进 iOS 推送,提高稳定性。
  • _Notification 表针对 iOS 推送消息增加 invalidTokens 字段,表示本次推送遇到的无效设备总数。

Android SDK v2.6.11

  • 改进文件上传策略提高小文件上传进度精度

iOS SDK v2.6.10.2

  • 修复聊天连接断开可能导致崩溃的问题
  • 修复崩溃统计开关无效的问题,添加开关设置完成回调接口
  • 修复一处内存泄露问题

02-10~02-11 云代码故障说明

2015-02-10 20:23 ~ 2015-02-11 00:02 期间,云代码请求间歇出现 502 和 404 的情况,部分应用出现应用「应用未部署」的提示。

原因是由于使用我们服务的部分应用短期流量剧增,服务器承压,导致了以下几点问题:

中央文件存储(下面简称「存储」)异常:开发者的云代码应用是统一保存在存储上,然后挂载到各个云代码服务器。所以出现一个问题:所有静态请求加载资源文件时,都会走存储来获取。流量非常大时就导致存储大量出错,导致文件读取缓慢或者无法读取(最终造成 502 或者 404 响应)。这是我们初期设计考虑不周。
服务器容量和带宽不足:流量成倍增长导致服务压力破表,带宽打满。

故障期间我们做了什么:

故障开始时,我们收到服务器 load 过高报警,对流量确认后迅速对服务器和带宽进行扩容。
扩容后开始将应用分散到新的服务器,load 下降,压力缓和。
随后又出现一波压力,扩容后的服务器并没有出现预期的压力承受能力,load 又迅速升高。
经过排查,发现存储出现大量错误,随即开始迁移云代码应用,重新挂载存储。这个期间存储异常状况的出现很不稳定,导致我们花了很多时间和精力在迁移应用,此时会导致部分应用出现「应用未部署」的情况。
最后我们将流量大的应用的代码拷贝到服务器本地磁盘,让这些应用的静态资源流量不再走存储,压力才慢慢缓解。

故障后我们做了什么:

我们改进了云代码,尽量降低对存储的依赖:开发者部署应用时,会将项目代码从存储拷贝到服务器本地磁盘,应用运行期间都是用本地磁盘的代码。如果应用出现问题,别的服务器的备份服务也会从存储上拷贝代码到本地磁盘,并提供服务;存储我们也做了容灾处理。所以最坏情况假设存储彻底挂掉,所有正在运行的服务是不受影响的,只是在存储切换期间无法重新部署应用。

云代码服务器扩容:我们增加了很多的服务器。经过这次问题,我们也对云代码服务器的容量极限和压力基准有了更清楚的了解,后期根据此基准我们会更有预见性的扩容。

在此向所有在此次故障中受到影响的用户致歉,我们将以一如既往认真谨慎的态度,继续为开发者们服务。

#006 晨读推荐

晨读推荐2

关于「晨读推荐」

由 LeanCloud 的工程师和设计师,以周为单位进行阅读分享,包含每位工程师和设计师想加入自己读到的「有价值、有趣味、有分享意义」的文章以及推荐理由。每周一和周三在微信公众帐号进行推送。

技术向 Geek Must Read

V8 之旅系列文章
LeanCloud 导读:
随着 web 技术发展,JavaScript 的地位越来越重要。V8 是出自 Google 之手的当前最先进的 JavaScript 引擎。此系列文章介绍了 V8 的一些内部实现细节,包括即时编译 / 垃圾回收 / 对象内存模型。对于对编程语言实现感兴趣,或者一直写 JavaScript,但对其实现好奇的同学,此文章作为 V8 的入门指引是再适合不过了。

工具向 The best tools

《Android Needs A Simulator, Not An Emulator》
LeanCloud 导读:
出自 Android 大牛 Jake Wharton 博客的一篇旧闻,主要吐槽 Android 系统模拟器种种不爽,也介绍了最新的模拟器 Genymotion。但本重点是在介绍两个 Simulator 工具 Robolectric 和 layoutlib。看到这里就觉得是在为 Robolectric 打广告,因为 Jake 本身也是 Robolectric 的贡献者,还自己为其写了 gradle 插件。这篇文章从开发者角度出发,介绍了为何我们需要的是 Robolectric 这类 JVM 虚拟工具,而不是模拟器。虽然没什么干活,但是写进了开发者心坎,值得推荐。

设计向 Design More,Design Better

《An exploration in Material Design》
LeanCloud 导读:
这是来自 feedly 的 Material Design 的实践经验分享。Material Desgin 是下一个阶段 Android UI 设计的主流和趋势,来自于同行的经验不仅仅是看到了如何做新的设计,还能够从新旧设计的对比中间找到增量改进的经验,而不是完全将应用的 UI 设计推到重来。

新知向 Ren chou jiu yao duo du shu

《Discrete Math for Computer Science Students》
LeanCloud 导读:
本书从程序设计的角度比较详细地介绍了一些数学知识在计算机科学领域的应用。不仅由浅入深地介绍各种常用的数学工具与方法,同时考虑到如何在程序设计中使用相关技术。除了涉及计数、数论、概率论、图论等内容,还通过大量篇幅阐述了不少程序员比较欠缺的知识点:归纳、递归、循环以及逻辑推导与证明。当然,其课后习题质量也比较不错,值得动手一试。