内容来自wwdc2020 视频源地址

 

First, we need to know what tagged pointers are.

首先,我们需要知道什么是tagged pointers。

We're gonna get really low-level here, but don't worry. Like everything else we've talked about, you don't need to know this. It's just interesting, and maybe helps you understand your memory usage a little better.

我们在这而说这个会说的比较底层,但不要担心。 就像我们已经讨论过的所有其他内容一样,您不需要了解这一点。 这很有趣,也许可以帮助您更好地了解内存使用情况。

Let's start by looking at the structure of a normal object pointer. Typically when we see these, they're printed as these big hexadecimal numbers. We saw some of these earlier.

让我们先看看在一个正常的对象指针的结构。 通常,当我们看到这些内容时,它们会以这些大十六进制数字的形式打印出来。 我们之前看过一些。

Let's break it out into the binary representation.

我们将其分解为二进制表示形式。

 We have 64 bits, however, we don't really use all of these bits.

总共有64位,然而我们并没有真正地使用到所有这些位。

Only these bits here in the middle are ever set in a real object pointer.

我们只在一个真正的对象指针中使用了中间的这些位。

The low bits are always zero because of alignment requirements. objects must always be located at an address that's a multiple of the pointer size.

由于需要对齐,低位始终为0。对象必须始终位于指针大小倍数的一个地址中。

The high bits are always zero because the address space is limited. we don't actually go all the way up to two to the 64.

由于地址空间有限,所以高位始终为0。实际上是用不到2^64的。

These high and low bits are always zero.

这些高位和低位总是0。

So, let's pick one of these bits that's always zero and make it a one.

所以,我们从这些始终为0的位中选择一位来置为1。

That can immediately tell us that this is not a real object pointer, and then we can assign some other meaning to all of the other bits.

这可以让我们立即知道这不是一个真正的对象指针,然后我们可以给其他所有位赋予一些其他的意义。

We call this a tagged pointer.

我们将这种指针称为tagged pointer。

For example, we might stuff a numeric value into the other bits.

例如,我们可以再其他位中塞入一个数值。

As long as we were to teach NSNumber how to read those bits, and teach the runtime to handle the tagged pointers appropriately, the rest of the system can treat these things like object pointers and never know the difference.

只要我们想教NSNumber如何读取这些位并让runtime适当地处理tagged pointer,系统的其他部分就可以把这些东西当做对象指针来处理,并且永远不会知道其中的区别。

And this saves us the overhead of allocating a tiny number object for every case like this, which can be a significant win.

这样就为我们节省了为每种情况分配一个小数目对象的开销,这是一个重大的改进。

Just a quick aside, these values are actually obfuscated by combining them with a randomized value that's initialized at process startup.

顺便说一下,这些值实际上是通过与进程启动时初始化的随机值相结合而被混淆的。

 

 

This is a security measure that makes it difficult to forge a tagged pointer.

这一安全措施使得很难伪造tagged pointer。

We'll ignore this for the rest of the discussion, since it's just an extra layer on top. Just be aware that if you actually try and look at these values in memory, they'll be scrambled.

在接下来的讨论中,我们将忽略这一点,因为它只是在顶部增加了一层,只是要注意,如果你真的试图在内存中查看这些值,它们会呗打乱。

So, this is the full format of a tagged pointer on Intel. The low bit is set to one to indicate that this is a tagged pointer.

所以这就是Intel上tagged pointer的完整格式。我们把低位设置为1,表示这是一个tagged pointer。

As we discussed, this bit must always be zero for a real pointer, so this allows us to tell them apart.

正如我们所讨论的,对于一个真正的指针,这个位必须始终为0,所以我们可以把它们区分开来。

The next three bits are the tag number. This indicates the type of the tagged pointer. For example, a three means it's an NSNumber, a six, that it's an NSDate.

接下来的三位是标签号。这表示tagged pointer的类型。例如3表示它是一个NSNumber,6表示这是一个NSDate

 

 

 

Since we have three tag bits, there are eight possible tag types.

由于我们有3个标签位,所以有8种可能的标签类型。

The rest of the bits are the payload. This is data that the particular type can use however it likes.

剩下的位是有效负载。这是特定类型可以随意使用的数据。

For a tagged NSNumber, this is the actual number.

对于tagged的NSNumber,就是实际的数字。

Now, there's a special case for tag seven. This indicates an extended tag. An extended tag uses the next eight bits to encode the type, allowing for 256 more tag types at the cost of a smaller payload.

现在标签7有一个特殊情况。它表示一个扩展标签。扩展标签使用接下来的8位来编码类型,允许多出256个标签类型,但是代价是减少有效负载。

This allows us to use tagged pointers for more types, as long as they can fit their data into a smaller space.

这使得我们可以将tagged pointer用于更多的类型,只要他们可以将其数据装入更小的空间。

This gets used for things like tagged UI colors or NSIndexSets.

这可用于标记诸如UIColor或者NSIndexSet这样的类。

Now, if this seems really handy to you, you might be disappointed to hear that only the runtime maintainer, that is Apple, can add tagged pointer types.

现在,如果这对你来说非常方便,你可能会感到失望,因为只有runtime维护者--Apple,能添加tagged pointer类型。

But if you're a Swift programmer, you'll be happy to know that you can create your own kinds of tagged pointers. If you've ever used an enum with an associated value that's a class, that's like a tagged pointer.

但是如果你是Swift的开发者,你会庆幸可以创建自己的tagged pointer类型。如果你曾经使用过一个具有关联值得枚举,那是一个类似于tagged pointer的类。

The Swift runtime stores the enum discriminator in the spare bits of the associated value payload.

Swift runtime将枚举判别器存储在关联值有效负载的备用位中。

What's more, Swift's use of value types actually makes tagged pointers less important, because values no longer need to be exactly pointer sized.

而且Swift对值类型的使用,实际上使得tagged pointer变得没那么重要了,因为值不再需要精确地指针大小。

For example, a Swift UUID type can be two words and held inline instead of allocating a separate object because it doesn't fit inside a pointer.

例如Swift UUID类型,可以是两个单词并保持内联,而不是分配一个单独的对象,因为它不适合在一个指针里面。

Now that's tagged pointers on Intel. Let's have a look at ARM.

现在说的这些都是Intel中的tagged pointer,我们来看下ARM中是什么样的。

On arm64, we've flipped things around.

在arm64上,这些都是反过来的。

Instead of the bottom bit, the top bit is set to one to indicate a tagged pointer.

最高位设置为1来表示tagged pointer,而不是最低位。

Then the tag number comes in the next three bits, and then the payload uses the remaining bits.

然后再接下来的3个位中出现标签号,剩下的位为有效负载。

Why do we use the top bit to indicate a tagged pointer on ARM, instead of the bottom bit like we do on Intel? Well, it's actually a tiny optimization for objc_msgSend.

为什么我们在ARM上使用高位来表示tagged pointer,而不是像Intel那样在低位表示呢?嗯,这实际上是对objc_msgSend的一个小优化。

We want the most common path in msgSend to be as fast as possible, and the most common path is for a normal pointer.

我们希望msgSend中最常见的路径可以尽可能地快,而最常见的路径是一个普通的指针。

We have two less common cases: tagged pointers and nil.

有两种不太常见的情况,tagged pointer和nil。

It turns out that when we use the top bit, we can check for both of those with a single comparison, and this saves a conditional branch for the common case in msgSend compared to checking for nil and tagged pointers separately.

事实证明,当我们使用最高位时,我们可以通过一次比较对这两个进行检查,相比于分开检查nil和tagged pointer,这就为msgSend中的常见情况节省了一个条件分支。

Just like in Intel, we have a special case for tag seven, where the next eight bits are used as an extended tag, and then the remaining bits are used for the payload.

和Intel中一样,对于标签7我们有一个特殊情况,接下来的8位被用作于扩展标签,其他的位用于有效负载。

 

 

Or that was actually the old format used in iOS 13. In this year's release, we're moving things around a bit. The tag bit stays at the top, because that msgSend optimization is still really useful.

或者说,这其实是iOS13使用的旧格式。在今年的版本中,我们做了一些改动。我们将标签保持在最高位,因为这对msgSend的优化还是非常有用的。

The tag number now moves to the bottom three bits.

标签号现在移到了最低的三个位。

The extended tag, if in use, occupies the high eight bits following the tag bit.

如果正在使用扩展标签,那么它会占据标签位后的高8位。

Why did we do this? Well, let's consider a normal pointer again.

为什么我们要这样做呢?好吧,让我们再来看看正常指针。

Our existing tools, like the dynamic linker, ignore the top eight bits of a pointer due to an ARM feature called Top Byte Ignore, and we'll put the extended tag in the Top Byte Ignore bits.

我们现有的工具, 如动态链接,会忽略指针的前8位,这是由于一个名为Top Byte Ignore的ARM特性,而我们会把扩展标签放在Top Byte Ignore位。

For an aligned pointer, the bottom three bits are always zero, but we can fiddle with that just by adding a small number to the pointer.

对于一个对齐指针,低位的3个位总是0,但是我们可以改变这一点,只需要通过在指针上添加一个小数字。

We'll add seven to set the low bits to one. Remember, seven is the indication that this is an extended tag.

我们将添加7以将低位设置为1。请记住7表示这是一个扩展标签。

And that means we can actually fit this pointer above into an extended tag pointer payload.

这意味着我们实际上可将上面这个指针放入一个扩展标签指针有效负载中。

The result is a tagged pointer with a normal pointer in its payload.

这个结果是一个tagged pointer以及其有效负载包含一个正常指针。

Why is that useful? Well, it opens up the ability for a tagged pointer to refer to constant data in your binary such as strings or other data structures that would otherwise have to occupy dirty memory.

为什么这会有用呢?对的,它开启了tagged pointer引用二进制文件中的常量数据的能力,例如字符串或者其他的数据结构,否则它们将不得不占用dirty memory。

Now, of course, these changes mean that code, which accesses these bits directly, will no longer work when iOS 14 is released later this year.

当然,现在这些变化意味着今年晚些时候iOS14发布时直接访问这些位的代码将会失效。

A bitwise check like this would've worked in the past, but it'll give you the wrong answer on future OSs, and your app will start mysteriously corrupting user data.

在过去,像这样的位检查是可以的,但在未来的OS上,这样会得到一个错误的结果,然后你的App会开始莫名其妙地破坏用户数据。

So, don't use code that relies on anything we've just talked about. Instead, you can probably guess what I'm gonna say, which is use the APIs.

所以不要使用那些依赖于我们所谈到的这些代码。相反,你大概可以猜到我要说什么,也就是要使用API。

Type checks like isKindOfClass worked on the old tagged pointer format, and they'll continue to work on the new tagged pointer format. All NSString or NSNumber methods just keep on working. All of the information in these tagged pointers can be retrieved through the standard APIs.

像isKindOfClass这样的类型检查,它们在旧的tagged pointer可以运行,在新的tagged pointer格式上也继续可用。所有的NSString或者NSNumber方法都可以使用。这些tagged pointer中的所有信息都可以通过标准的API来检索。

It's worth noting this also applies to CF types as well.

值得注意的是,这也适用于CF类型。

We don't want to hide anything and we definitely don't want to break anybody's apps. When these details aren't exposed, it's just because we need to maintain the flexibility to make changes like this, and your apps will keep working just fine, as long as they don't rely on these internal details.

我们不想隐藏任何东西,也绝对不想破坏任何人的App。当这些细节没有公开的时候,这仅仅是因为我们需要保持灵活性来进行此类更改,并且只要你的App不依赖这些内部细节它们就可以正常工作。

So, let's wrap up. In this talk, we've seen a few of the behind-the-scenes improvements that have shrunk the overhead of our runtime, leaving more memory available to you and your users.

我们总结一下。在本次讲座中,我们看到了一些幕后的改进,这些改进减少了runtime的代价,将更多的内存留给你和用户。

You get these improvements without having to do anything except, maybe, consider raising your deployment target.

你不需要做任何事情就可以获得这些改进,可能出了需要考虑提高你的部署目标外,

To help us make these improvements each year, just follow a simple rule.

要帮助我们每年做出这些改进,只要遵循一个简单的规则。

Don't read internal bits directly. Use the APIs.

不要直接读取内部的位,请使用API。

 

搬运结束,搬运工小张下班。

posted on 2020-09-21 19:57  Gary_小咖  阅读(231)  评论(0编辑  收藏  举报