作者:Mike Ash,原文链接,原文日期:2016-04-15
译者:Yake;校对:numbbbbb;定稿:shanks

在我开始做 Friday Q&A 之前,我曾发表过一些关于常见操作性能测试的文章,并对结果进行了讨论。最近的一篇是在 2008 年 10 月 5 日,在 10.5 的 Mac 系统和最早的 iPhone 操作系统上。已经好长一段时间没有更新了。

之前的文章

如果你想和之前的文章做对比,可以阅读下述内容:

  • Mac(10.5)

  • Mac(10.4)

  • iPhone OS 1

(注意苹果的手机操作系统直到 2010 年才被称为iOS

概述

性能测试可能会很危险。测试报告看起来通常很不自然,除非你有特定的可以模仿真实应用场景的应用。这些特殊的测试肯定不真实,并且测试结果可能无法真实地反应项目的实际性能。虽然不能对所有的事都给出确切的结果,但它能让你了解大概的数量级。

测量高速操作是很难的一件事,比如 Objective-C 的消息发送或者是数学运算。由于现在 CPU 有复杂的设置与并行机制,一个操作独立花费的时间可能与它在复杂的真实项目中花费的时间并不相符。如果操作足够独立,将这类操作的代码添加到代码中时,CPU 可以并行处理,那可能根本不会增加那个操作本身执行需要的时间。另一方面,如果它占用了重要资源,就可能会让运行时间大大增加。

性能也可能依赖于一些外部因素。许多现代 CPU 在低温环境下运行很快,但是变热后就会慢下来。文件系统的性能将会依赖于硬件以及文件系统的状态。即使是相关的性能也会有所不同。

当性能特别重要时,你总是希望能测量并做图表分析,以便确切地知道在你的代码中哪里花费了时间,这样就直到应该把注意力集中在哪里。如果能找到代码中降低性能的地方,你一定会很开心。

总之,对各种操作的速度有个大致的概念将会十分有用。也许这能避免你在文件系统中存一大堆数据。为之付出一些努力是值得的,不过最终可能只是少发了一条消息,这么算又不太值。总之,谁也说不准结果如何。

方法

你可以在GitHub中获取这些测试的代码。

代码是用Objective-C++写的,核心的性能测试是用 C 语言写的。目前我对 Swift 的了解还不够深入,因此无法测试 Swift 的性能。

基础的技术很简单:把目标操作放入一个循环中持续几秒钟。用总的运行时间除以循环次数得到操作每次执行的时间。循环时间是硬编码的,我会尽量延长测试时间,从而减少环境因素的影响。

我试图将循环本身的开支考虑在内。这种开支对于较慢操作的影响完全不重要,但是对于较快操作的影响却相当大。因此,我会对一个空的循环进行计时,然后从其他测试的时间中减去每次循环的时间。

在有些测试中,测试代码可能会被流水线机制(校对注:CPU 的一种优化机制)优化,从而和被测试的代码并行。这使得那些测试时间惊人地短,从而导致完全错误的结果。考虑到这些因素,一些高速操作会被手动展开,每次循环会执行十次测试,我希望通过这种方式让结果变得更真实。

测试的编译与运行没有经过优化。这与我们通常的做法相反,但是我觉得对测试来说这样做更好。对于那些几乎完全依赖于外部代码的操作,例如与文件相关的操作或者 JSON 解析,结果没什么变化。但对于简单的操作例如数学计算或者方法调用,编译器很可能会直接把毫无意义的测试代码优化掉。此外,优化也会改变循环的编译方式,这会使得计算循环本身执行时间变得很复杂。

Mac 测试用的是我的 2013 年的 Mac Pro:3.5GHz,Xeon E5 处理器,系统是 10.11.4。iOS 测试用的是我的 iPhone 6s ,系统是iOS 9.3.1.

Mac 测试

下面是 Mac 测试的数据。每一个测试都会列出测试内容、测试循环次数、测试需要的总时间以及每一次操作花费的时间。所有的时间都减掉了循环本身的消耗。

Name    Iterations    Total time (sec)    Time per (ns)
16 byte memcpy    1000000000    0.7    0.7
C++ virtual method call    1000000000    1.5    1.5
IMP-cached message send    1000000000    1.6    1.6
Objective-C message send    1000000000    2.6    2.6
Floating-point division with integer conversion    1000000000    3.7    3.7
Floating-point division    1000000000    3.7    3.7
Integer division    1000000000    6.2    6.2
ObjC retain and release    100000000    2.3    23.2
Autorelease pool push/pop    100000000    2.5    25.2
Dispatch_sync    100000000    2.9    29.0
16-byte malloc/free    100000000    5.5    55.4
Object creation    10000000    1.0    101.0
NSInvocation message send    10000000    1.7    174.3
16MB malloc/free    10000000    3.2    317.1
Dispatch queue create/destroy    10000000    4.1    411.2
Simple JSON encode    1000000    1.4    1421.0
Simple JSON decode    1000000    2.7    2659.5
Simple binary plist decode    1000000    2.7    2666.1
NSView create/destroy    1000000    3.3    3272.1
Simple XML plist decode    1000000    5.5    5481.6
Read 16 byte file    1000000    6.4    6449.0
Simple binary plist encode    1000000    8.8    8813.2
Dispatch_async and wait    1000000    9.3    9343.5
Simple XML plist encode    1000000    9.5    9480.9
Zero-zecond delayed perform    100000    2.0    19615.0
pthread create/join    100000    2.8    27755.3
1MB memcpy    100000    5.6    56310.6
Write 16 byte file    10000    1.7    165444.3
Write 16 byte file (atomic)    10000    2.4    237907.9
Read 16MB file    1000    3.4    3355650.0
NSWindow create/destroy    1000    10.6    10590507.9
NSTask process spawn    100    6.7    66679149.2
Write 16MB file (atomic)    30    2.8    94322686.1
Write 16MB file    30    3.1    104137671.1

这个表中最突出的是第一条。16-byte memcpy测试每次用时不到一纳秒。请看生成代码,虽然我们关闭了优化,但是编译器很聪明地将memcpy调用转换成了一系列的mov指令。这点很有趣:你写的方法调用不一定真的会调用这个方法。

一个真正的 C++ 方法调用和拥有IMP缓存的ObjC消息发送消耗相同的时间。它们真正做的操作一模一样:一个通过函数指针实现的非直接方法调用。

一个普通的Objective-C消息发送,和我们想的一样,相对较慢。然而,objc-msgSend的速度依然震惊到我了。它先是执行了一个完整的哈希表查询,然后又间接跳向了结果,一共只花了 2.6 纳秒!这差不多是 9 个 CPU 周期。同样的操作在 10.5 系统中需要超过 12 个周期,这么看性能确实有不小的提升。如果你只是做Objective-C的消息发送操作,这台电脑每秒钟可以执行四亿次。

使用NSInvocation来调用方法相对较慢。NSInvacation需要在运行时创建消息,和编译器在编译时做的事一样。幸运的是,NSInvocation在实际项目中一般不会成为性能瓶颈。不过和 10.5 对比,它的速度有所下降,一个NSInvocation调用大约花了之前两倍的时间,即使这次测试是在更快的硬件环境下进行的。

一对retainrelease操作一共消耗 23 纳秒。修改一个对象的引用计数必须是线程安全的,必须使用原子操作,这在纳秒级 CPU 中代价很高。

autoreleasepool比之前快了很多。在之前的测试中,创建并销毁一个自动释放池花费了超过 300 纳秒的时间。这次测试中,只用了 25 纳秒,自动释放池的实现已经完全改写了,新的实现快的多,所以这没什么好惊讶的。释放池曾经是NSAutoReleasePool类型的实例,但现在使用运行时方法来完成,只需要做一些指针操作。25 纳秒,你可以放心地把@autoreleasepool放在任何需要自动释放的地方。

分配和释放 16 字节花费的时间没有多大变化,但是较大空间的分配速度显著提升。过去分类和释放 16MB 大约需要 4.5 微秒的时间,但现在只需要 300 纳秒。一般应用都会做很多的内存分配工作,所以这是个很大的提升。

Objective-C对象的创建速度也提升了很多,从过去的 300 纳秒到现在的 100 纳秒。显然,一个典型的应用会创建并销毁很多 Objective-C 对象,所以这个提升效果显著。另一方面,创建并销毁一个对象的时间,相当于发送 40 个消息,所以这还是一个代价很高的操作。另外,大多数对象创建和销毁需要的时间都远大于一个简单的NSObject实例。

dispatch_queue的测试在不同的操作中表现出了有趣的差异。dispatch_sync在一个非竞争队列中特别快,时间在 30 纳秒以下。GCD 很高效,在本例中不做任何跨线程的调用,所以一共只需要执行一次加锁和释放操作。dispatch_async花费的时间就长得多,它需要先找到一条工作线程来使用,唤醒线程,然后在线程中执行任务。和 Objective-C 对象相比,创建并销毁一个diapatch_queue对象要快很多。GCD 能够共享很多内容,所以创建队列成本很低。

我这次增加了JSON以及plist的编码和解码测试,这个测试之前没有做过。由于 iPhone 的普及,这类操作受到越来越多的关注。这个测试编码并解码了一个包含三个元素的字典。正如预期的那样,它比消息发送这种简单并且低级的事务要慢,但仍在微妙的范围内。有趣的是,JSON比属性列表表现更好,哪怕是二进制的属性列表也比JSON慢,出乎意料。这可能是因为JSON用途更广,因此获得更多关注;也可能是因为JSON格式解析起来更快;或者是因为用一个只包含三个元素的字典测试不太合适,数据量更大时它们之间的速度差别可能会改变。

同步任务所需时间很多,大概是dispatch_async时间的两倍。看起来,运行时循环还有很多有待提升的地方。

创建一个pthread并等它终止,是另外一个相对较为重量级的操作,时间大概在将近 30 纳秒。因此我们理解了为什么GCD只使用一个线程池,并且只在必要时才创建新的线程。然而,这个测试已经比过去的测试快多了,同样的测试,过去需要花超过 100 微秒的时间。

创建一个NSView实例很快,大约 3 微秒。不同的是,创建一个NSWindow就慢得多,耗费大约 10 微秒时间。NSView是较为轻量的一种结构,它代表了界面中的一片区域, 而NSWindow则代表了窗口服务器中的一块像素缓存。创建一个NSWindow类型的对象需要让窗口服务创建必要的结构,还需要很多设置工作,给NSWindow类型的对象添加所需的各种内部对象,例如标题栏上的视图。这样说来,相比NSWindow,我更推荐使用NSView

文件存取肯定很慢。SSD已经提升了很多性能,但还是有很多的耗时的操作。所以只在必要的时候存取文件,能不用就别用。

iOS 测试

下面是 iOS 的测试结果

Name    Iterations    Total time (sec)    Time per (ns)
C++ virtual method call    1000000000    0.8    0.8
IMP-cached message send    1000000000    1.2    1.2
Floating-point division with integer conversion    1000000000    1.5    1.5
Integer division    1000000000    2.1    2.1
Objective-C message send    1000000000    2.7    2.7
Floating-point division    1000000000    3.5    3.5
16 byte memcpy    1000000000    5.3    5.3
Autorelease pool push/pop    100000000    1.5    14.7
ObjC retain and release    100000000    3.7    36.9
Dispatch_sync    100000000    7.9    79.0
16-byte malloc/free    100000000    8.6    86.2
Object creation    10000000    1.2    119.8
NSInvocation message send    10000000    2.7    268.3
Dispatch queue create/destroy    10000000    6.4    636.0
Simple JSON encode    1000000    1.5    1464.5
16MB malloc/free    10000000    15.2    1524.7
Simple binary plist decode    1000000    2.4    2430.0
Simple JSON decode    1000000    2.5    2515.9
UIView create/destroy    1000000    3.8    3800.7
Simple XML plist decode    1000000    5.5    5519.2
Simple binary plist encode    1000000    7.6    7617.7
Simple XML plist encode    1000000    10.5    10457.4
Dispatch_async and wait    1000000    18.1    18096.2
Zero-zecond delayed perform    100000    2.4    24229.2
Read 16 byte file    1000000    27.2    27156.1
pthread create/join    100000    3.7    37232.0
1MB memcpy    100000    11.7    116557.3
Write 16 byte file    10000    20.2    2022447.6
Write 16 byte file (atomic)    10000    30.6    3055743.8
Read 16MB file    1000    6.2    6169527.5
Write 16MB file (atomic)    30    1.6    52226907.3
Write 16MB file    30    2.3    78285962.9

最明显的是,它和 Mac 测试的结果很相似。看看过去的测试结果,iPhone 上的结果都相对较慢。一个 Objective-C 消息发送在 Mac 大约为 4.9 纳秒,在 iPhone 上要花很长时间,约为 200 纳秒。一个 C++ 的虚函数调用在 Mac 上花费大约 1 纳秒的时间,iphone上需要 80 纳秒。malloc/free 一段小的内存在 Mac 上约为 50 纳秒,但是在 iPhone 上需要大约 2 微秒的时间。

对比新旧测试,在如今的移动设备时代,很多事情都发生了变化。大多数情况下 iPhone 的数据只比 Mac 差一点,有些操作甚至更快。例如,自动释放池在 iPhone 上是相当快的。我猜ARM64更擅长执行自动释放池的代码。

读写小文件是 iPhone 的一大弱点。16MB 的文件测试与 Mac 的测试结果差不多,但是 16 字节的文件测试 iPhone 花了 Mac 10 倍的时间。相比 Mac,iPhone 的存储设备吞吐量很高,但是有一些额外的延迟。

结论

关注性能可以让你写出高质量的代码,不过你只需要记住项目中常见操作的大致性能。性能会随着软件和硬件的提升发生变化。在过去的几年中 Mac 已经有了不错的提升,不过 iPhone 的进步更大。只用了 8 年时间,iPhone 就从比 Mac 慢一百倍进化到了同等性能。

今天就到此为止吧,下次再来讨论一些更有趣的东西。Friday Q&A 是由读者的建议驱动的,所以如果你想在某次的讨论中看到某个主题,请把它发送到这里

本文由 SwiftGG 翻译组翻译,已经获得作者翻译授权,最新文章请访问 http://swift.gg。

[2016 版] 常见操作性能对比相关推荐

  1. 数据科学教程:R语言与DataFrame[2016版]

    数据科学教程:R语言与DataFrame[2016版] r HarryZhu 2016年03月16日发布 保存 标签:至少1个,最多5个 r× 开发语言 平台框架 服务器 数据库和缓存 开发工具 系统 ...

  2. 2016版单词的减法_在2016年最大的电影中,女性只说了27%的单词。

    2016版单词的减法 by Amber Thomas 通过琥珀托马斯 在2016年最大的电影中,女性只说了27%的单词. (Women only said 27% of the words in 20 ...

  3. JAVA程序员一定知道的优秀第三方库(2016版)

    几乎每个程序员都知道要"避免重复发明轮子"的道理--尽可能使用那些优秀的第三方框架或库,但当真正进入开发时,我却经常发现他们有时并不知道那些轮子在哪里.最近,我在业余时间带几个年轻 ...

  4. 注册公司流程和费用(2016版)

    公司注册流程及费用(2016版) 2014年3月国家<新公司法>正式实施,降低了公司注册门槛,简化了公司注册流程.2015年"大众创业.万众创新"的热潮再次把注册公司推 ...

  5. HTML5期末大作业:京东网站设计——仿2016版京东首页(1页) HTML+CSS+JavaScript 大学生网页作品 电商网页设计作业模板 学生网页制作源代码下载

    HTML5期末大作业:京东网站设计--仿2016版京东首页(1页) HTML+CSS+JavaScript 大学生网页作品 电商网页设计作业模板 学生网页制作源代码下载 常见网页设计作业题材有 个人. ...

  6. 视频教程-JavaScript从入门到精通2016版教学视频-JavaScript

    JavaScript从入门到精通2016版教学视频 19年软件开发经验,设计开发40多个大型软件,10年从事高等教育,主要为java系列课程,带你轻松进入java生涯. 赖国荣 ¥39.00 立即订阅 ...

  7. 全国计算机一级office2016版,全国一级计算机基础及MS-Office应用课件2016版.ppt

    全国一级计算机基础及MS-Office应用课件2016版 5.3 插入图形与图像 为了在演示过程中对内容做更加清晰明确的介绍,用户可以通过插入图形或图片的形式,通过图文并茂的方式让观看者对演示内容进行 ...

  8. 京东2016版首页改版前端总结

    为什么80%的码农都做不了架构师?>>>    深圳的天气总是多变,前一段时间还是凉意浓浓,似乎要步入冬天了,最近却又变得炎热起来,气温骤升,让人措手不及.正如我们负责的业务一样,一 ...

  9. 三星galaxy a9android,【三星2016版GALAXYA9评测】最新版智能管理器_三星 2016版GALAXY A9_手机评测-中关村在线...

    系统上三星Galaxy A9(A9000)搭载基于Android 5.1.1定制的最新TouchWiz,风格上也非常契合年轻消费群体,UI及系统应用都偏向淡雅配色和轻盈的扁平设计. 而作为一款中高端定 ...

最新文章

  1. Windows下MySQL下载安装、配置与使用
  2. Hacker's Browser
  3. Ipython 和 python 的区别
  4. 详解Python模块化编程-自定义函数
  5. emplace与insert
  6. arcgis引用样式无符号_【技术积累】arcgis制图应用:符号制作
  7. 三行情书代码_用三行代码优化您的交易策略
  8. python 异步与io
  9. PHP函数之HTMLSPECIALCHARS_DECODE
  10. Maven学习总结(58)—— 常用的 Maven 镜像地址和中央仓库地址汇总
  11. 年前辞职-WCF入门学习(5)
  12. ME53N采购申请查询时增加屏幕的增…
  13. 怎样为Mac视频添加音频
  14. win10如何在当前目录打开cmd窗口
  15. CUDA+OpenCV 绘制朱利亚(Julia)集合图形
  16. malloc、calloc、realloc
  17. 看小姐姐用动图展示 10 大 Git 命令
  18. 深入浅出讲解FOC控制与SVPWM技术
  19. 给vmware7虚拟机xp整理磁盘碎片
  20. 题解 修改字符串 DDP基础题

热门文章

  1. CAD图块无法分解怎么办?CAD块分解教程
  2. 2022年蓝桥杯Python程序设计B组思路和代码分享
  3. 王者荣耀-数模论文分享(虽然结果我自己都不信)
  4. substr() 方法
  5. win系统在虚拟机中启动黑屏的处理办法
  6. java 根据开始日期 ,需要的工作日天数 ,计算工作截止日期,并返回截止日期
  7. 03、【电脑维修】防火墙丢失,找不到 windows firewall服务, windows defender firewall服务被禁用或防火墙无法打开
  8. 《数据结构C语言版》-栈的概念和栈的实现
  9. x86_64汇编之三:x86_64汇编和x86_32汇编的区别
  10. ES 实现数据库or查询效果