Objective-c: Method Cache

Source: Internet
Author: User
Tags throw exception

Summary

As long as we use objective-c, we deal with method calls every day. We all know that the method of OBJECTIVE-C resolution is dynamic, but in the bottom of a method is how to find, how the method cache and how the operation is little known. This paper probes into the implementation of the Objective-c method resolution (methods resolving) and method cache in the runtime layer from the source point of view.

Brief introduction

The authors are from the group's iOS development team, the American hotels Travel Group. We are committed to creating value, improving efficiency and striving for excellence. Welcome to join us (please send your CV to email [email protected]).

This article is a study of the OBJECTIVE-C runtime source, the main analysis of the objective-c in the runtime layer of the method of resolution process and method cache, the content includes:

    • From the message resolution.

    • Who does the cache live for?

    • What is the method cache?

    • Caching and hashing

    • 100,000 why

    • Cache-Performance optimized for Balm?

    • Optimized, never-ending

From the message resolution.

As we all know, calling a method in Objective-c is like this:

[object MethodA];

This means we want to call the MethodA of object.

But what does it mean to call a method inside a objective-c, or is it just like C + +, that any non-virtual method is compiled into a unique symbol that is called to look up the symbol table, find the method and then invoke it?

The answer is in the negative. When a method is called in Objective-c, the runtime will translate the call into a

Objc_msgsend (ID self, SEL op, ...)

And how is objc_msgsend specifically distributed? Let's look at the source code of the runtime layer objc_msgsend.

In OBJC-MSG-ARM.S, the code for Objc_msgsend is as follows:

(Ps:apple in order to optimize the performance of objc_msgsend, this file is written in the assembly, but even if we do not understand the assembly, detailed comments can give us a glimpse of its true face)

ENTRY objc_msgsend# Check whether receiver isNilteq A1, #0beq lmsgsendnilreceiver# Save registers and load receiver'S class for CachelookupSTMFD sp!, {A4,v1}ldr v1, [A1, #ISA]# Receiver isnon-Nil:search the Cachecachelookup A2, V1, lmsgsendcachemiss# cache Hit (ImpinchIP) and Cachelookup returns with Nonstret (eq)Set, restore registers and CALLLDMFD SP!{a4,v1}bx ip# cache Miss:go Search the method Listslmsgsendcachemiss:ldmfd sp!, {a4,v1}b _objc_msgsend_uncachedlmsgsendnilreceiver:mov A2, #0bx lrlmsgsendexit:end_entry objc_msgsendstatic_entry objc_msgsend_uncached# Push stack framestmfd sp!, {a1-a4,r7,lr}add R7, SP, # -# Loadclassand Selectorldr A3, [A1, #ISA]/*class = Receiver->isa*//*selector already in A2*//*receiver already in A1*/# do the lookupmi_call_external (__class_lookupmethodandloadcache3) MOVE IP, a1# Prep forforwarding, Pop stack frame and call Impteq v1, v1/*set Nonstret (eq)*/LDMFD SP!, {a1-A4,R7,LR}BX IP

As you can see from the code above, the Objc_msgsend (for ARM platforms) message distribution is divided into the following steps:
    • Determine if receiver is nil, that is, the first parameter of Objc_msgsend self, which is the object to which the method to invoke belongs

    • Search from the cache, find it, distribute it, or

    • Take advantage of the objc-class.mm in _class_lookupmethodandloadcache3 (why there is such a strange way.) The end of this article will explain) method to find Selector

      • If GC is supported, ignore methods for non-GC environments (retain, etc.)

      • From the method list of this class, look for selector, if found, populate the cache, and return selector, otherwise

      • Look for the method list of the parent class and look up in turn until selector is found, populated into the cache, and returns selector, otherwise

      • Call _class_resolvemethod, if you can dynamically resolve for a selector, do not cache, the method returns, otherwise

      • Forward this selector, otherwise

    • Error, throw exception

Who does the cache live for?

From the above analysis we can see that when a method in the "upper" class, compared with the "lower layer" (the upper and lower layers of the inheritance relationship) object to call, if there is no cache, then the entire lookup chain is quite long. Even if the method is in this class, when the method is more, every time to find is also a laborious thing.

Consider one of the following calling procedures:

 for int 0 100000; + +i)    {*myobject = myobjects[i];    [MyObject MethodA];}

When we need to call a method hundreds of thousands of times or more, the consumption of the lookup method becomes very significant.

Even if our usual non-mass calls, unless a method is called only once, the cache is useful. At runtime, so many objects, so many method calls, the time saved is also very considerable.

What is the method cache?

In the face of the source code, no secret principle, we look at the source of the method of caching in the end is what, in objc-cache.mm, Objc_cache is defined as follows:

struct Objc_cache {    uintptr_t mask;             /*  */    uintptr_t occupied           ; *buckets[1];};

Well, the definition of Objc_cache looks simple, it contains the following three variables:

1), Mask: can be considered to be the current maximum index (starting from 0), so the cached size (total) is mask+1

2), occupied: occupied slots, because the cache is in the form of a hash table exists, so there will be empty slots, and occupied represents the current number of occupied

3), buckets: Hash table represented by an array, cache_entry type, each Cache_entry represents a method cache

(Buckets defined at the end of Objc_cache, indicating that this is a variable-length array)

The definition of Cache_entry is as follows:

struct {    SEL name;      // same layout as struct Old_method    void *unused;    IMP imp;   // same layout as struct Old_method} Cache_entry;

The Cache_entry definition also contains three fields, namely:

1), name, cached method name

2), unused, reserved fields, not yet used.

3), IMP, method realization

Caching and hashing

The cached storage uses a hash table.

Why use a hash table? Since the hash table is faster to retrieve, let's look at how the method cache is hashed and retrieved:

// Scan for the first unused slot and insert there. //  //  minimum size is 4 and we resized at 3/4 full. Buckets = (cache_entry * *) cache->buckets;  for (index = Cache_hash (sel, cache->mask);      ! = NULL;       = (index+1) & cache->mask) {    //  empty= entry;

This is a code snippet for storing a method in the method cache, and we can see that the SEL was hashed and found an empty slot placed in the buckets, and the cache_hash is defined as follows:

#define Cache_hash (SEL, Mask) ((uintptr_t) (SEL) >>2) & (Mask))

This code uses the SEL's pointer address and mask to do a simple calculation.

The cache from the hash table is written using assembly language (compiled for highly optimized objc_msgsend). We look at the Cachelookup method inside the OBJC-MSG-ARM.MM:

. Macro Cachelookup/*Selreg, Classreg, Misslabel*/MOVE R9, $0, LSR #2          /*index = (sel >> 2)*/Ldr A4, [$1, #CACHE]/*cache = Class->cache*/add A4, A4, #BUCKETS/*buckets = &cache->buckets*//*Search the Cache*//*A1=receiver, A2 or A3=sel, R9=index, A4=buckets, $1=method*/1: LDR IP, [A4, #NEGMASK]/*mask = Cache->mask*/and R9, R9, IP/*Index &= Mask*/LDR $1, [A4, R9, LSL #2]/*method = Buckets[index]*/TEQ $1, #0                  /*if (method = = NULL)*/Add R9, R9, #1              /*index++*/beq $2                      /*Goto Cachemisslabel*/LDR IP, [$1, #METHOD_NAME]/*Load Method->method_name*/TEQ $0Ip/*if (method->method_name! = sel)*/BNE 1b/*Retry*//*Cache hit, $ = = Method Triplet Address*//*Return triplet in $ and imp in IP*/LDR IP, [$1, #METHOD_IMP]/*imp = Method->method_imp*/. Endmacro

Although it is a compilation, but the comments are too exhaustive, it is not difficult to understand, or to seek a hash, go to buckets, find not to follow the rules of the hash conflict continue downward, until the end.

100,000 why

After understanding the definition of the method cache, we ask a few questions and answer

    • Where does the method cache exist?

Let's go through the definition of class, in OBJECTIVE-C 2.0, the definition of class is roughly the same (see OBJC-RUNTIME.MM)

struct _class_t {  struct _class_t *Isa;   struct _class_t *superclass;   void *cache;   void *vtable;   struct _class_ro_t *ro;  };

We see that there is a cache field in the definition of the class, yes, all caches of the class exist on Metaclass, so each class has only one copy of the method cache, not one for each class object.

    • Does the parent class method cache only the parent class, or does the subclass cache the method of the parent class?

In the first section of Objc_msgsend's retrospective, we can see that even the methods taken from the parent class will be in the method cache of the class itself. When a parent object is used to invoke that method, a copy is also cached in the parent class's metaclass.

    • Is there a limit to the method cache size of the class?

To answer this question, we need to look at the source code, in objc-cache.mm there is a variable defined as follows:

/* When _class_slow_grow was Non-zero, any given cache was actually grown * only on the   odd-numbered times it becomes Full On the even-numbered   * times, it is simply emptied and re-used.  When the this flag is zero   ,*/  staticconstint1;

Instead of looking at further code snippets, we can see the answer to the question only from the comments. Note that when _class_slow_grow is a value of 0, the size of the method cache will grow only if the method caches the odd number of times (using more than 3/4 slots) (the cache will be emptied, otherwise the hash value will be incorrect); When the odd number of times is full, The method cache is emptied and re-exploited. If the _class_slow_grow value is 0, then each time the method cache is full, its size will grow.

So in the case of the problem alone, the answer is no limit, although the value is set to 1, the method cache size will be slower, but there is no limit.

    • Why is the method list of the class not directly into the hash list, make list, also want to cache separately, how much trouble?

This question, I think there are the following three reasons:

    • The hash list is not sequential, the Objective-c method list is a listing, is sequential, objective-c in the search method will follow the list in turn, and the category method in front of the original method list, need to be found first, If you use the hash method directly, the order of the methods cannot be guaranteed.

    • The list method also preserves many other properties besides selector and Imp.

    • The hash table is empty slot, it wastes space

Cache-Performance optimized for Balm?

Non-Also, even if there is a method of objective-c itself cache, we still have a lot of methods to optimize the optimization of space, for this matter, this article is very detailed, we can take a self-observation http://www.mulle-kybernetik.com/ Artikel/optimization/opti-3-imp-deluxe.html (strongly recommended, although we generally do not encounter the need for such intensity optimization, but this spirit and thought is worthy of our study)

Optimized, never-ending

At the end of the article, let's answer the question in the first section: "Why is there _class_lookupmethodandloadcache3 this method?" ”

The implementation of this method is as follows:

/************************************************************************ _class_lookupmethodandloadcache.* Method lookup for dispatchers only. Other CODE should with Lookupimp (). * This lookup avoids optimistic cache scan because the dispatcher* already tried that.** ********************************************************************/IMP _class_lookupmethodandloadcache3 (IDobj, sel sel, Class CLS) {    returnLookupimporforward (CLS, sel, obj, YES/*Initialize*/, NO/*Cache*/, YES/*Resolver*/);}

If you look at the method name alone, this method should look for a method from the cache and the method list, but as described in the first section, before invoking this method, we have not been able to find the method from the cache, so this method avoids the process of scanning the cache lookup method, but directly from the method list. From the Apple code comments, we can also fully understand this point. Desperate pursuit of perfection and performance is a quality.

Postscript

This article is Objective-c Runtime source study of the second chapter, mainly on the Objective-c method of resolution and method cache to do the analysis. The runtime source code can be downloaded from http://www.opensource.apple.com/tarballs/. If there is any mistake, please correct me.

Objective-c: Method Cache

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.