深入分析 objc_msgSend

原文:http://vagase.me/blog/dig-objc-msgsend/


在Objective-C中,所有的[receiver message]都会转换为objc_msgSend(receiver, @selector(message));Objective-C Runtime)。所以相比c/c++这势必是有性能影响,下面就分析objc_msgSend源码看看这个cost到底有多大,并给出在特殊情况下改进方案。

「objc_msgSend() Tour」 的系列文章中,已经通过对objc_msgSend汇编源码进行的详细分析,总结出objc_msgSend的处理流程:

  1. Check for ignored selectors (GC) and short-circuit.
  2. Check for nil target.
    • If nil & nil receiver handler configured, jump to handler
    • If nil & no handler (default), cleanup and return.
  3. Search the class’s method cache for the method IMP(use hash to find&store method in cache)
    • If found, jump to it.
    • Not found: lookup the method IMP in the class itself corresponding its hierarchy chain.
      • If found, load it into cache and jump to it.
      • If not found, jump to forwarding mechanism.

在另外一个博客中「Obj-C Optimization:Method and function call innards」通过分析运行时的汇编代码,给出了更加直观的objc_msgSend运行流程。而且该大神在「Obj-C Optimization: The faster objc_msgSend」给出了objc_msgSend实现的c语言版本,这个理解起来就更加容易了:


id c_objc_msgSend( struct objc_class /* ahem */ *self, SEL _cmd, ...)
{
struct objc_class *cls;
struct objc_cache *cache;
unsigned int hash;
struct objc_method *method;
unsigned int index;

if( self)
{
cls = self->isa;
cache = cls->cache;
hash = cache->mask;
index = (unsigned int) _cmd & hash;

do
{
method = cache->buckets[ index];
if( ! method)
goto recache;
index = (index + 1) & cache->mask;
}
while( method->method_name != _cmd);
return( (*method->method_imp)( (id) self, _cmd));
}
return( (id) self);
 
recache:
/*
* internal call _class_lookupMethodAndLoadCache to
* look method and load into cache
*/
return( 0);
}


objc_msgSendview raw

This Gist brought to you by GitHub.

所以第一次调用某个method的时候,会需要运行500多行指令去寻找method并添加到cache;但是之后只需要运行30多行指令,通过hash方法直接在cache中找到相应method。所以可以认为:「objc_msgSend的cost大概在30多行指令」。这个cost是十分小的,对于现代CPU来说毛毛雨都不算,所以不用为objc_msgSend带来的cost操碎了心。

但是即使再微小的cost一旦累计多了,也可能带来很大的耗时,特别是一些常常会被调用的核心代码。所以必要时可以这样优化,通过methodForSelector 直接获得selector的IMP,将Objective-C method转换为c调用。于是我写了个小测试:


#define LOOP 90000000
#define START { clock_t start, end; start = clock();
#define END end = clock(); \
printf("Cost:%f\n", (double)(end - start) / CLOCKS_PER_SEC * 1000); }
 
- (void)_internalCall{
// Do nothing...
}
 
- testObjectiveCMethod {
START
 
for (NSUInteger i = 0; i < LOOP; ++i) {
[self _internalCall];
}

END
}
 
 
- testCCall {

SEL sel = @selector(_internalCall0);
IMP call = [self methodForSelector:@selector(_internalCall)];

START

for (NSUInteger i = 0; i < LOOP; ++i) {
call(self, sel);
}

END
}


boost objc_msgSendview raw

This Gist brought to you by GitHub.

测试结果为:Objective-C Call Cost: 527.266ms;C Call Cost:330.790ms

从上面测试表明,C调用方式比OC调用方式将近快了2倍。如果运行速度成为了程序的瓶颈,采取上面的方法给程序提速不失为一个不错的选择。


IMP缓存的实现可以参考: http://blog.csdn.net/zhchaoo/article/details/8727104

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值