ARM64
在iPhone5S,苹果就已经转向ARM64位处理器了。通常说处理器多少位,一般涉及到整数寄存器大小以及指针的宽度。而现代CPU这两个大小一般也是相等的。所以64位一般也就是说CPU有64位整数寄存器和64位宽的指针了。
不过我还不是很明白,为什么这两个大小要设计的一样大。寄存器个数和宽度翻番都是明显可以提升性能的。而翻倍的指针宽度好像只是浪费更多的内存了?虽然提高了地址空间,但真的是提高的太多了。32位CPU,可使用的地址空间有2^32,即4G个,对于每个存储单元1byte说,就是最大支持4GB的内存。可直到今年的iPhone11 Pro发布才刚超过这个大小。64位宽的指针,内存地址空间达到2^64个,也就是17,179,877,980G个地址,远远远远远远超过需要的地址空间。指针大小的翻倍,使程序运行需要不必要的更多内存。
所以,尽管指针有64 bit,但并不是所有的bit都真正使用了。Mac OS X 上用了47位,而手机iOS上更是只用了33位。那么64位这么有富余的指针地址,苹果都玩出来了什么花
//内存类似这么一个数组,地址从0到0X01 FFFFFFFF
//每个地址里是一个存储单元,一个存储单元是8位,一个byte
//所以33位的8GB = 8G * 1byte
memory = [
0x00 00 00 00 00 00 00 00:[0,1,0,1,0,0,0,0],
0x00 00 00 00 00 00 00 01:[0,1,0,1,0,0,0,0],
0x00 00 00 00 00 00 00 02:[0,1,0,1,0,0,0,0],
0x00 00 00 00 00 00 00 03:[0,1,0,1,0,0,0,0],
0x00 00 00 00 00 00 00 04:[0,1,0,1,0,0,0,0],
0x00 00 00 00 00 00 00 05:[0,1,0,1,0,0,0,0],
0x00 00 00 00 00 00 00 06:[0,1,0,1,0,0,0,0],
0x00 00 00 00 00 00 00 07:[0,1,0,1,0,0,0,0],
0x00 00 00 00 00 00 00 08:[0,1,0,1,0,0,0,0],
...
...
0x00 00 00 01 FF FF FF F9:[0,1,0,1,0,0,0,0],
0x00 00 00 01 FF FF FF FA:[0,1,0,1,0,0,0,0],
0x00 00 00 01 FF FF FF FB:[0,1,0,1,0,0,0,0],
0x00 00 00 01 FF FF FF FC:[0,1,0,1,0,0,0,0],
0x00 00 00 01 FF FF FF FD:[0,1,0,1,0,0,0,0],
0x00 00 00 01 FF FF FF FE:[0,1,0,1,0,0,0,0],
0x00 00 00 01 FF FF FF FF:[0,1,0,1,0,0,0,0]
]
内存地址空间大致分布如下,地址仅代表从低向高方法,栈地址使用由高向低,堆由低向高:
| 0x000000 00 00 00 00 00 | 保留 |
|---|---|
| 0x000000 00 00 00 F0 00 | _Text |
| 0x000000 00 00 0F 00 00 | _Data |
| 0x000000 00 0F 00 00 00 | 字符串常量 |
| 0x000000 00 0F 0F 00 00 | 未初始化数据 |
| 0x000000 00 0F F0 00 00 | 初始化数据 |
| 0x000000 00 0F FF 00 00 | 堆区 |
| 0x000000 00 FF 00 F0 00 | 栈区 |
| 0x000000 01 FF FF FF FF | 内核区 |
isa
在Objective-C里,什么是对象?
typedef struct objc_object *id;
struct objc_object {
private:
isa_t isa;
...
void initInstanceIsa(Class cls, bool hasCxxDtor);//对象初始化
}
OC里用id表示ObjC对象,这里可以看到id指针就是一个指向含isa字段的结构体。isa在32位CPU上实际就是它的类结构体地址,类结构体里有父类地址以及方法地址等。
而在ARM64上,我们说了一个地址实际只用了33位,如果isa完整的保存这个64位地址0x00000001 A5FC91F0,首先高位的28位肯定是0,后面三位也肯定是0(因为对象是8位对齐的,CPU并不是任意地址都会直接访问,为了效率,只会访问被4整除的地址),显然每个对象里保存一样的31个0完全是浪费。
define ISA_MASK 0x0000000ffffffff8ULL
union isa_t
{
Class cls;
uintptr_t bits;
struct {
uintptr_t indexed : 1;//为0则是直接地址,1则是含有其他信息
uintptr_t has_assoc : 1;//1显示该对象objc_setAssociate对象
uintptr_t has_cxx_dtor : 1;//1为有c++销毁函数
uintptr_t shiftcls : 33; //类地址
uintptr_t magic : 6;//判断是否已初始化
uintptr_t weakly_referenced : 1;//1为该对象有弱指针
uintptr_t deallocating : 1;//该对象正在释放
uintptr_t has_sidetable_rc : 1;//判断该对象引用计数是否过大
uintptr_t extra_rc : 19;//该对象额外的指针引用
};
}
isa类型是isa_t,上面可以看到isa_t是个union联合体类型。联合体是指这里面的字段指的是同一段内存空间,只是以不同的字段看待不同理解而已。
我们可以看到33个bit长度的shiftcls,这就是类的真正地址。它为什么会在第4个位置呢?因为对象地址是8位对齐的,也就是最低三位地址肯定是0的,用低三个位存储特殊bit,要获取真实地址时,和低三位为0的ISA_MASK进行与操作可以直接获得真实地址。
我们可以看到普通对象有一个isa,也就是8个字节,接下来会存储类属性值。那么一个NSNumber对象存一个数字123使要用多少空间呢?下面图可以看到至少要20个字节,因为对象实际是以16个字节为最小单位递增分配的,实际上至少是24个字节。
有点费空间,那么苹果对这个情况是怎么省着点用的呢
Tagged Pointer
苹果的回答是标记指针。我们先看使用标记指针的结果是多少,8个字节。
从上面这个地址我们可以看到,这明显不是一个正常的内存地址。28个高位不再是0了。是的,这就是标记指针,指针用本身表示了这个对象,也就不用在堆里分配空间创建对象了。
//ObjC原生 Tagged Pointer支持的类,index偏移
OBJC_TAG_NSString = 2,
OBJC_TAG_NSNumber = 3,
OBJC_TAG_NSIndexPath = 4,
OBJC_TAG_NSManagedObjectID = 5,
OBJC_TAG_NSDate = 6,
我们现在有一个值为1的NSNumber对象,打印地址得到0x91f75f31d1a0f417,63位为1表示是TaggedPointer,60到62位表示类型,0到59位是data
关于类对象
在ObjC里,类也是一种对象,也就是类对象。
typedef struct objc_class *Class;
struct objc_class : objc_object {
// Class ISA; //8byte
Class superclass; //8byte
cache_t cache; // 方法缓存,16byte
uintptr_t bits; //8byte,class_rw_t,方法、属性、协议等
}
struct cache_t {
struct bucket_t *_buckets;
uint32_t _mask; //分配用来缓存bucket的总数
uint32_t _occupied; //目前实际占用的缓存bucket的个数。
}
struct bucket_t {
private:
uintptr_t _key;
IMP _imp;
}
从上面的定义,就可以看出来,类对象继承自objc_object。另含有父类,方法缓存,及bit三个字段。总大小为40个字节。
NSLog(@"NSObject对象实际需要的内存大小: %zd", class_getInstanceSize(NSObject.class));//8
NSLog(@"NSObject对象实际分配的内存大小: %zd", malloc_size((__bridge const void *)(fatherClas)));//16
Class objectMeta = objc_getMetaClass("NSObject");
NSLog(@"Object类实际需要的内存大小: %zd", class_getInstanceSize(objectMeta));//40
类对象是它对应的Meta Class的实例,所以我们打印MetaClass的实例大小,可以获取类的大小,打印出来的也正是40。
参考资料
1.神经病院 Objective-C Runtime 入院第一天—— isa 和 Class