Swift Method Dispatching

When announcing Swift, Apple described it as being much faster than Objective-C. On the web, there is a number of comparsions juxtaposing speed of both languages. In my opinion, the way Swift dispatches method invocations has the biggest impact on its performance.

Leaving the assessment of method dispatch performance aside, let’s focus on understanding how it works in Swift.

Before looking into the Swift method dispatching, it is worth making a short recollection of the Objective-C method dispatching.

Objective-C Method Dispatching

During the compilation process, Clang translates each method invocation into a call to the objc_msgSend function (or one of its variations), passing an object, method selector and parameters as arguments.

For example, the [object message: param] invocation gets translated into the following call: objc_msgSend(object, @selector(message:), param, nil).

Basically, the objc_msgSend functions family handles method dispatching in Objective-C. These functions are implemented in the assembly language and opensourced. Let’s not go deeper into Apple sources, but instead try to understand how the objc_msgSend function works by looking at the following pseudo-code:

 

First, it obtains a class of the passed obj object (this runs in constant time).

 

Then, it tries to look for a method implementation in a class cache.

 

Finding a selector in the cache is a relatively fast operation — as fast as a look-up in the hashmap. However, in the case of cache miss, the program has to take a slower path and call class_getMethodImplementation, which looks for implementation by scanning all the selectors defined in the class and its ancestors, down to the root of the inheritance tree.

 

This method is relatively slow.

Finally, objc_msgSend cleans a stack frame and jumps directly to the method implementation, passing the object, method selector and parameters (that is why objc_msgSend is often called a trampoline rather than a function).

 

Before looking at the Swift method dispatching, let’s get familiar with two important notions.

Name Mangling

Swift allows a programmer to define many entities with the same name, e.g. the same class names in different modules or the same method names in different classes. Moreover, Swift allows method overloading. Linker resolves external references by symbols names, when combining object files. That is why, the compiler needs to produce unique symbol names and does it by encoding every entity name. This process of name encoding is called name mangling.

Let’s take a look at an example of Swift method signatures and corresponding mangled symbol names:

 

They are defined in the same class and have the same name, so we deal with a simple example of method overloading. It is worth noting, that Swift method takes an object instance as a first argument to use it as a self pointer (you can read more about this on Ole Begemann’s blog).

Now, take a look at symbol names being a result of mangling the above signatures:

 

The first method takes a string type parameter marked as S in the first symbol. The second method takes an int type parameter marked as i in the second symbol.

Obviously, the above should not give any reason for concern in the everyday development work. But when there is a need, a symbol can be easily demangled using this handy command passing the symbol as a parameter:

 

If you would like to read more about method mangling, there is no better article than the one on the NSBlog.

Before diving into the Swift method dispatching, let’s take a look at one more thing. Namely, let’s see how Swift compiler translates a code into a binary executable.

Compilation

Swift compilation consists of several phases.

At first, a Swift Front End translates a Swift code into a high-level, platform agnostic SIL (Swift Intermediate Language). Everyone can examine SIL using the swiftc --emit-sil command. Secondly, a SIL Optimizer takes SIL, optimises it on a high-level of abstraction and provides an output in the IR (Intermediate Representation) format, which is low-level, yet platform agnostic. IR can be examined using the swift --emit-ir command. Then, IR is optimized. In the last phase, a Code Generator uses the optimised IR to generate a machine code. Anyone can view the output of this phase in the assembly language by using the swift -S command.

Again, all you need to be aware of is that there are a couple of phases and that you can use an early phase output to analyse a program code. If you wish to read more about Swift compilation, please refer to John Siracusa’s article.

Now, let’s get to the the point…

Virtual Method Table

Virtual Method Table is a mechanism used in Swift and many other languages to support run time method binding. We are going to investigate what it looks like in Swift.

Let’s take the following two classes:

 

They present an Agent class able to perform some basic Kung Fu moves: punch, kick, jump and block. For the sake of this example, let’s assume that the Agent’s defence mechanism cannot be overridden (block method declared as final). Then, we have an agent Smith subclass, which overrides the jump method — let’s assume that Smith jumps a bit differently than other agents. Furthermore Smith can laugh (evilly but still).

Now, let’s take a look at the following snippet from the generated SIL code:

 

A bit more transparent view may be of help:

 

The above shows that the SIL virtual method table is basically a dictionary that maps method names to their implementations (function pointers). In the Agent class vtable, every method maps to its original implementation from the same class. The vtable of the Smith subclass starts with all the methods defined in the Agent class, and finishes with methods declared in the Smith subclass. The jump method was overridden in the Smith subclass and so it is clearly visible in the second vtable that the Agent.jump method name maps to the Test.Smith.jumpimplementation.

The block method mapping cannot be seen — in fact, no mapping is necessary, because the method is declared as final and it has only one well-known implementation.

Let’s get into details by skipping IR and looking directly into the Assembly. Here is a code snippet from the output of the swiftc -S command:

 

The above snippet shows some similarity to SIL vtables. The first line presents .globl __TMdC4Test5Agent — a declaration of a global symbol for the direct type metadata of the Agent class, followed only by a set of pointers. Then, there is .globl __TMdC4Test5Smith — a declaration of a global symbol for the direct type metadata of theSmith subclass, followed by another set of pointers. Basically, direct type metadata has been defined as an array of pointers, so… what happened to the dictionary-like structure of the vtable? Let’s look at the test method code to see what is going on:

 

The above method is used by the Matrix to make a simple test of the Agent’s Kung Fu abilities. It takes an Agentinstance and invokes its jump, punch, kick and block methods. Now, let’s skip SIL and IR and go directly to the assembly of this method:

 

I must warn you that I have cleaned the above listing a little bit. I have removed code lines which run in constant time and are not significant for this investigation (that is ARC code and local variables code). But this is still impressive — Swift code maps almost directly to processor instructions! So what is there left? The first line contains just a global function symbol declaration. Look at the second line. rdi is a register in the Intel x86-64 architecture that usually holds a value of a function’s first argument. In our case, a value of the first argument is a pointer to the Agent compatible instance. A Swift instance is a structure and its very first field is a pointer to its metadata, so the assembly code can obtain a pointer to the class metadata basically by dereferencing the instance pointer.

In the test function assembly, there are also three function calls to some computed addresses and one function call to a well-known address. Remember that the block method has been marked final, so there is no need to use vtable — a direct call to its implementation is sufficient.

To clarify the code above, let’s look at it in the form of a pseudo-code:

 

It does not compile, but it makes the situation more clear. In the second line, an argument pointer is being dereferenced and casted to a metadata pointer (metadata will serve as vtable). Then, it adds an offset of 0x58 to the metadata pointer and dereferences it. Wait a minute… let’s look back at the direct type metadata in the assembly! (I have just added some exemplary file offsets in the left column):

 

It may appear strange and probably be an implementation detail, but an object’s metaclass pointer points to the third element of its direct type metadata. Don’t be concerned about it — this is irrelevant in this investigation. Important is that the agentKungFuTest() function code accesses pointer at offset of 0x58 bytes from that place, that is:

  • __TFC4Test5Smith4jumpfS0_FT_T_ - Test.Smith.jump in case of a Smith instance
  • __TFC4Test5Agent4jumpfS0_FT_T_ - Test.Agent.jump in case of an Agent instance

Look at the agentKungFuTest pseudo-code:

 

The jump variable points to the Test.Smith.jump implementation and this implementation is simply called.

Let’s take a look at the metadata again. It becomes obvious that the vtable dictionary-like structure has not been lost. It has just morphed into a form in which a mapping key is defined as an offset in the metadata. Something like that:

 

Optimizations

All the SIL and assembly code listings in this article were produced without the optimization -O compiler flag. This is because the purpose was to find out how Swift dispatches methods in the worst case scenario. But you should be aware that the use of the -O flag can drastically change the final machine code:

  • non-final methods can be invoked by direct function calls, when the compiler knows the exact class of a target instance,
  • compiler can even skip a function call and inline a method implementation in place of the method invocation,

so the final code can be even faster :)

Summary

Let’s wrap up! That was a long journey through the depths of Swift. You saw that Swift uses vtables for method dispatching. Because of that, method dispatching in Swift is much simpler and faster — so more battery saving. Unfortunately, in the case of a regular app, the speed gain will probably be insignificant, unless the app does some complex computations.

By its very definition, vtable dispatch has one big disadvantage — it lacks dynamism so commonly used by Objective-C programmers and in Cocoa frameworks. If you decide to code in Swift, you will probably end up mixing in some Objective-C.

https://allegro.tech/2014/12/swift-method-dispatching.html

Swift Method Dispatching — a summary of my talk at Swift Warsaw的更多相关文章

  1. 给swift程序猿留下深刻印象的10个Swift代码

    通过使用单行代码完成同样的 10 个练习,我们来看看 Swift 和其他语言之间的较量. 将数组中每个元素的值乘以 2 使用map来实现 var arr = [1,2,3,4]; var newArr ...

  2. Swift技术之如何在iOS 8下使用Swift设计一个自定义的输入法 (主要是NSLayoutConstraint 的使用)

    当前位置: > Swift新手入门 > Swift技术之如何在iOS 8下使用Swift设计一个自定义的输入法 时间:2014-09-10 16:49来源:未知 作者:啊成 举报 点击:5 ...

  3. Swift互用性:与 Cocoa 数据类型共舞(Swift 2.0版)-b

    本节内容包括: 字符串(Strings) 数值(Numbers) 集合类(Collection Classes) 错误(Errors) Foundation数据类型(Foundation Data T ...

  4. Swift要点:从Objective-C开发者的角度看Swift

    代码环境是Xcode6.3-Beta3. Swift已经极大的改变了开发iOS应用的方式.本文中,我会列出Swift的几个重点,并且和Objective-C一一做出对比. 注意,本文不是Swift的入 ...

  5. 《从零开始学Swift》学习笔记(Day2)——使用Web网站编写Swift代码

    Swift 2.0学习笔记——使用Web网站编写Swift代码 原创文章,欢迎转载.转载请注明:关东升的博客 Swift程序不能在Windows其他平台编译和运行,有人提供了一个网站swiftstub ...

  6. Use Swift Dynamic Framework (如何科学地引用第三方 Swift 库)

    转自:http://andelf.github.io/blog/2014/07/07/use-swift-dynamic-library/ CocoaPods 由于完全使用静态链接解决方法,过度依赖 ...

  7. [Swift]LeetCode228. 汇总区间 | Summary Ranges

    Given a sorted integer array without duplicates, return the summary of its ranges. Example 1: Input: ...

  8. 用Swift重写公司OC项目(Day2)--创建OC与Swift的桥接文件,进而调用OC类库

    昨天把项目中的图标以及启动转场图片弄好了,那么今天,我们可以开始慢慢进入到程序的编写当中了. 由于swift较新,所以类库还不够完善,但是不用担心,苹果早就出了解决方案,那就是使用桥接文件,通过桥接文 ...

  9. Swift版音乐播放器(简化版),swift音乐播放器

    这几天闲着也是闲着,学习一下Swift的,于是到开源社区Download了个OC版的音乐播放器,练练手,在这里发扬开源精神, 希望对大家有帮助! 这个DEMO里,使用到了 AudioPlayer(对音 ...

随机推荐

  1. Restful传递数组参数的两种方式

    第一种,直接传递数组 js直接传递数组 var data = ["123","456"];that.loadDictionarys(data).subscrib ...

  2. DSP广告系统架构及关键技术解析(转)

    广告和网络游戏是互联网企业主要的盈利模式 广告是广告主通过媒体以尽可能低成本的方式与用户达成接触的商业行为.也就是说按照某种市场意图接触相应人群,影响其中潜在用户,使其选择广告主产品的几率增加,或对广 ...

  3. Servlet3.0中使用getPart进行文件上传

    这个先进些,简单些,但书上提供的例子不能使用,到处弄了弄才行. servlet代码: package cc.openhome; import java.io.InputStream; import j ...

  4. Linux下统计某个目录的文件个数(转)

    1.统计某文件夹下文件个数,不包括子文件夹 比如:统计/home下.jpeg文件的个数 ls -l "/home" | grep ".jpeg" | wc -l ...

  5. SharePoint 2013:解决爬网出错的问题

    现象: 以前一直正常的爬网突然无法顺利完成,总是在进行到某个部分就停滞不前. 调查: 在查看了log文件后,发现了这条错误 06/24/2014 11:14:51.86   NodeRunnerQue ...

  6. eclipse中Client/Server程序生成exe

    先建两个Java Project项目,一个写Client,一个写Server端程序,程序大致为一个Server端建立监听某个port.多个Client端能够连接,实现例如以下: 1.      Ser ...

  7. hdu1068 Girls and Boys --- 最大独立集

    有一个集合男和一个集合女,给出两集合间一些一一相应关系.问该两集合中的最大独立集的点数. 最大独立集=顶点总数-最大匹配数 此题中.若(a,b)有关.则(b,a)有关.每个关系算了两次,相当于二分图的 ...

  8. 计算cost--全表扫描

    以下教大家怎样手工算出oracle运行计划中的cost值. 成本的计算方式例如以下: Cost = (        #SRds * sreadtim +        #MRds * mreadti ...

  9. nginx+tomcat反复请求

    好久不写技术文章了,越发的认为单纯的讲技术没啥意思.怪不得知乎越来越火.由于大家都喜欢看故事.不喜欢硬生生的技术文章.笔者今天就来就给大家讲故事:) 近期站点压力突然增大,把带宽都占满了,訪问网页发现 ...

  10. CentOS 安装 MRTG 软件完成后的 403 Forbidden(转载)

    用 yum 安装 MRTG 並设定好之后也把 apache 的 httpd.conf 加上 mrtg 的目录,但 http://server/mrtg 卻一直出現 403 Forbidden.在 ht ...