如何通过一个下午的时间和乐器来大幅改进您的应用程序 — Sam Soffes
发表于
我是 在推特上吹牛 关于我如何通过一些简单的调整使我的应用程序变得更好。我想写一篇关于我所做的真正有帮助的文章,这可能会帮助大多数人。这些内容有点特定于应用程序,但我认为您会看到与您的应用程序的相似之处。
症状
当您第一次登录时,我的应用程序从网络中提取大量数据并将其放入核心数据中。通过使用该应用程序,我注意到性能一开始完全糟糕,然后又恢复正常。 (我的表格视图全部以 60fps 滚动,但我会将其保存到另一篇文章中。抱歉。不得不把它扔在那里。我很自豪。)这很令人不安,因为它通常工作得非常好,(好吧,现在我已经不再吹嘘我的细胞了)所以我进行了调查。
正如您所知,我正在通过后台线程完成所有网络、数据解析和插入核心数据的操作 NSOperationQueue。
问题
使用对象分配工具运行 Instruments 后,我注意到在下载所有这些数据时我使用了大约 22MB 的内存。在我看来,这太高了。我会把它添加到要搞乱的东西列表中。
我还注意到我的 NSDate 使用计时器工具解析 ISO8601 日期字符串(将日期放入 JSON 的标准方法)的类别大约需要 7.4 秒。完全不能接受。添加到列表中。
闲逛了一段时间后,我注意到很多时间都花在了我的一个 NSString 类别,特别是在 NSRegularExpression。这听起来很烦人,所以我把它留到最后。
解决方案
记忆
我对如何在将大量 JSON 字符串转换为 NSManagedObjects。我的猜测是大量的对象需要自动释放,但是 NSAutoreleasePool 直到操作完成才被排出。解决这个问题的简单方法是 添加一个合适的位置 NSAutoreleasePool 围绕问题代码。这需要几次尝试才能到达正确的位置。我会将其放在我认为大多数临时对象正在创建的位置,然后观察对象分配工具以确保它变得更平坦。
这是我的第一次尝试:

看看它是如何上升并急剧下降,然后持续一段时间然后最终下降的?这表明还有另一个循环嵌套在更深处,周围应该有一个水池。对于第一个,它做了一点,然后就耗尽了(可能是因为它在该操作中做了更少的事情)。由于第二个巨大的驼峰(请注意其峰值为 23MB 左右)暂时不会下降,因此我知道要寻找更深层次的另一个循环。希望这是有道理的。一旦你进去了,跌跌撞撞地走一段路后,它就会突然袭击你。你会看到的。
将其移至更嵌套的循环后,结果如下:

一旦我把它放在正确的位置, 整个过程只使用了不到 2MB 的内存! 分数!下一个问题。
日期的东西
约会的事情让我困惑了一段时间。我正在使用 ISO8601Parser ( ISO8601Parser 的子类 NSFormatter)与 NSDateFormatter。查看计时器工具后,我发现大部分时间都花在系统类中,例如 NSCFCalendar。我以为有更好的方法。我尝试切换回 NSDateFormatter,但这效果不佳,而且记忆力和速度仍然不佳。
作为免责声明,我只关注 Objective-C。我喜欢它。我不是那种总是说“嘿,我们应该用 C 重写这个”的工程师,但是嘿,我们应该用 C 重写这个。我做到了……结果令人震惊!
这是代码:
#include
+ (NSDate *)dateFromISO8601String:(NSString *)string {
if (!string) {
return nil;
}
struct tm tm;
time_t t;
strptime((string cStringUsingEncoding:NSUTF8StringEncoding), "%Y-%m-%dT%H:%M:%S%z", &tm);
tm.tm_isdst = -1;
t = mktime(&tm);
return (NSDate dateWithTimeIntervalSince1970:t + ((NSTimeZone localTimeZone) secondsFromGMT));
}
- (NSString *)ISO8601String {
struct tm *timeinfo;
char buffer(80);
time_t rawtime = (self timeIntervalSince1970) - ((NSTimeZone localTimeZone) secondsFromGMT);
timeinfo = localtime(&rawtime);
strftime(buffer, 80, "%Y-%m-%dT%H:%M:%S%z", timeinfo);
return (NSString stringWithCString:buffer encoding:NSUTF8StringEncoding);
}
看吧,这并不太疯狂。 使用 C 日期工具将我的日期解析时间从 7.4 秒缩短到 300 毫秒。谈论性能提升! (我更新了 SSTookit 的 NSDate 类别以使用这个新代码。)
正则表达式
我有几个 NSString 我的应用程序中用于执行各种操作的类别。其中一些在我试图优化的过程中被调用。我深入研究了时间分析仪并意识到 (NSRegularExpression regularExpressionWith...) 花了很多时间。这完全有道理,因为它会编译您的正则表达式以供稍后使用,而我每次都这样做。简单的解决方案:
- (NSString *)camelCaseString {
static NSRegularExpression *regex = nil;
if (!regex) {
regex = ((NSRegularExpression alloc) initWithPattern:@"(?:_)(.)" options:0 error:nil);
}
// Use regex...
return string;
}
这实际上是最简单的部分:)
结论
因此,一旦掌握了它的窍门,使用 Instruments 来追踪缓慢或错误的代码就非常容易了。如果您是新手,请从泄漏仪器开始。您的应用程序中不应该有任何(已知的)泄漏。
一旦你把它记下来(或者在试图追踪它时感到非常沮丧,你放弃并转向其他事情),接下来就使用对象分配工具。您可以观察图表并查看有多少对象处于活动状态。如果你看到一个永远不会下降的大峰值,那么你很可能有大量的内存,你可能不需要这些内存,但仍然有一个引用,因此它不会出现在泄漏中。在进行大量处理的循环周围添加自动释放池总是有帮助的。
最后,使用时间分析器工具查看哪些内容花费了很长时间,并对其进行优化。这是最有趣的,因为可以很容易地看到发生了什么以及您通过刚刚所做的更改取得了多少改进。使该工具发挥作用的关键是左侧的复选框。仅打开 Objective-C 或切换倒转堆栈树确实很有用。
这很难
不要感到难过,尤其是如果您是新手。这东西很难。我上面列出的所有解决方案都非常简单。我几乎花了一整天的时间来思考这几件事。您花费的大部分时间将用于追踪问题。修复它们通常非常简单,尤其是在完成几次之后。这很难。你很聪明。 🙂
