[译]静态派发优于动态派发——从性能分析角度

原文:Static Dispatch Over Dynamic Dispatch

作者:Shubham Bakshi

译者注: dispatch 可以理解为调度或者派发。参照 message-dispatch system 一般被译为消息派发系统本文中全部译为派发

点赞评论,希望能在你的帮助下越来越好

作者:@iOS成长指北,本文首发于公众号 iOS成长指北,欢迎各位前往指正

如有转载需求请联系我,记住一定要联系哦!!!

在上一篇[1]文章中,我解释了 Swift 编程语言中可用的各种派发技术。每当我们听到派发技术这个术语时,脑海中立即浮现的两种技术是静态派发和动态派发。

我强烈建议你阅读我之前的文章[1](如果还没有的话),以便你可以了解一些调度技术。

但是,如果你想知道它们究竟是什么,那就继续吧!

一切是怎么开始的

很久以前,苹果有一篇文章[2]说,静态派发优于动态派发。因此,你应该始终默认使用静态派发,并且仅在必要时切换为动态派发。

在 Swift 项目中有这样一篇文章,《Writing High-Performance Swift Code》[3] 同样说明了要 Reducing Dynamic Dispatch

之所以选择静态派发而不是动态派发来提高性能的根本原因是,在静态派发的情况下,编译器在编译时就能够知道要调用某个类的哪种方法实现。这样,编译器可以使用一些优化技术,例如设置一些标志,或者可能的话,将调用转换为内联派发(这是最快的)!

而在动态派发的情况下,编译器只能在运行时才能确定为某个类调用具体哪个方法实现,即基类方法还是子类方法。

这使得使用静态派发的性能优于动态派发。

如何实现静态派发

现在,按照本文所述,有三种方法可以实现静态派发或减少动态派发:

  • final 关键字:这确保了特定的类永远不会被子类化,特定的方法永远不会被重写,因此永远不会有动态调度。

  • private关键字:这限制了方法或变量对类本身的可见性。根据文章:

    这使编译器可以找到所有可能覆盖的声明。缺少任何此类覆盖的声明,可使编译器自动推断final关键字,并删除对方法和属性访问的间接调用。

    这意味着,只要我们将该方法或变量标记为 private,编译器都会执行搜索来判断该方法或变量在其他任何地方是否被重写,如果发生重写,就将生成一个编译时错误。如果找不到任何替代行为,则会将其隐式标记为 final

  • Whole Module 优化:这是一个编译器标志 -whole-module-optimization,默认情况下,Xcode 8 以后创建的新项目都会启用该标志。要点是,当我们不使用该标志(-wmo)时,Swift 编译器将分别编译属于一个模块的所有 .swift 文件。这限制了编译器添加某些优化(如内联),因为它将分别编译所有文件,因此编译器不知道不同的类及其方法之间的关系。当我们使用 -wmo 时,Swift 编译器会将所有这些 .swift 文件编译在一起,从而可以添加优化。如果你想更多地了解 -wmo,这里有很好的详细文档[4]

最后呢

当我读完这篇文章后,脑海中只有一件事——证据在哪里?

我知道到目前为止,我们已经研究了许多理论概念,但是我们是程序员。我们喜欢代码。还有什么比用代码来证明这些概念更好的证明呢?

小测验

我创建了一个小型的性能测试项目[5],它不过是一堆 Swift 类和一些单元测试。这是我能找到的测试代码性能/速度的唯一方法。你可以克隆仓库并亲自查看。

这里,我只使用了 final 关键字,但是你也可以使用其他两种方法。我将简要介绍一下项目的组成部分以及如何运行测试用例。

在这个项目中,只有两个文件对我们很重要:

  1. StaticDispatch.swift ——它包含我们将用于测试用例的所有类。
  2. PerformanceTesterTests.swift —— 它位于 PerformanceTesterTests 下,PerformanceTesterTests 是我们单元测试的文件组,其中包含我们的测试用例。

我在这两个文件中都添加了注释,使其更易于解释。

运行测试用例——

  • PerformanceTesterTests.swift 文件中取消第 36 行的注释,这个方法是静态派发。

  • 单击第 33 行上的菱形图标以运行测试用例。

dispatch_1.png

这将启动 iPhone 模拟器。一旦模拟器加载完成,它将运行测试用例。

  • 测试用例完成后,你可以在右侧看到运行该测试用例所花费的时间。

dispatch_2.png

  • 它可能报 no baseline fixed 或类似的内容,这时你需要单击图像中的灰色勾号(你可能需要首先取消灰色勾号然后再次选中),这将打开一个弹出窗口。像这样:

dispatch_3.png

  • 现在点击 Edit -> Accept -> Save。这将以此时间为基准,并且所有比较都将在我们保存的该基准时间上进行。

  • 就这样!注释当前行(第 36 行),取消注释下一行(记住,一次只取消注释一行),使用第 33 行再次运行测试用例,然后自己查看性能差异。它将向你显示我们设置的基线(静态调度基线)和当前测试用例之间的差异的百分比。

一些观察

  • 既不是 final 类也不是子类的类

dispatch_4.png

  • 类不是 final,而是子类

dispatch_5.png

  • 派生类(或子类)

dispatch_6.png

因此,总结一下,作为一个良好的实践,你应该首先将类标记为 final,如果需要继承,请移除 final 关键字。这确保了编译器将执行优化,从而提高了代码的性能。

参考资料

  1. Static vs Dynamic Dispatch in Swift: A decisive choice: medium.com/flawless-ap…

  2. Increasing Performance by Reducing Dynamic Dispatch:developer.apple.com/swift/blog/…

  3. Writing High-Performance Swift Code:github.com/apple/swift…

  4. Whole Module Optimizations:swift.org/blog/whole-…

  5. PerformanceTester:github.com/bakshioye/P…


如果你有任何问题,请直接评论,如果文章有任何不对的地方,请随意表达。如果你愿意,可以通过分享这篇文章来让更多的人发现它。

感谢你阅读本文! ?

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享