OC底层原理03:内存对齐原理和malloc源码分析
获取内存⼤⼩的三种⽅式
sizeof
:这是⼀个
操作符
,课操作的数据有
基本数据类型
、
指针
、
对象
。由于它不是函数,所以在
编译时就确定了⼤⼩。
class_getInstanceSize:获取
实例变量的成员变量⼤⼩之和
,
8
字节对齐,⽤之前需要导⼊头⽂件
objc/runtime.h
。
malloc_size:
获取系统实际开辟的内存⼤⼩
,⽤之前需导⼊头⽂件
malloc/malloc.h。
结构体内存对齐
⾸先⾃定义两个结构体,打印两个结构体的内存⼤⼩。
打印struct内存⼤⼩
从上图可以看到两个结构体的成员变量⼀样,但是由于
定义的顺序不⼀样
,导致了最后的
内存⼤⼩也不⼀样
。这种现象就是
iOS
中的内存字节对
齐。
根据上篇⽂章的内存对齐原则来⼿动计算⼀下两个结构体的内存⼤⼩。StructA:
a是⼀个
char
类型,占
1个字节。
b是⼀个
double
类型,占
8
个字节。并且要从
8
的整数倍开始存取,所以a后⾯要补上
7
个字节的空数据。
c是⼀个
short
类型,占
2个字节。
d是⼀个
int
类型,占
4
个字节。并且要从
4
的整数倍开始存取,所以c后⾯要补上
2
个字节的空数据。
所以StructA总的⼤⼩为:((1(a)+7(空数据)+8(b)+2(c)+2(空数据)+4(d)+7)>>3<<3=24StructB:
b是⼀个
double
类型,占
8
个字节。
d大骨汤面 是⼀个
int
类型,占
4
个字节。
c是⼀个
short
类型,占
2
个字节。
d是⼀个
char
类型,占
1个字节。
所以StructB总的⼤⼩为:((8(b)+4(d)+2(c)+1(d)+7)>>3<<3=16再来探索⼀下结构体嵌套的情况。
结构体嵌套
⼿动计算⼀下StructC的内存⼤⼩。
a是⼀个
char
类型,占
1个字节。
b是⼀个
double
类型,占
8
个字节。并且要从
8
的整数倍开始存取,所以a后⾯要补上
7
个字节的空数据。
c是⼀个
short
类型,占
2个字节。
structA是⼀个
StructA
类型,前⾯已经算出为
24
个字节。并且要从StructA内部最⼤的成员(
double8
字节)整数倍开始存取,所以c后⾯要补
上
6个字节空数据。
d是⼀个
int
类型,占
4个字节。
所以StructA总的⼤⼩为:((1(a)+7(空数据)+8(b)+2(c)+6(空数据)+24(structA)+4(d)+7)>>3<<3=56
所以当我们定义结构体的时候,为了优化内存,减少开销,可以将成员从⼤到⼩排列。下⾯是⼀些iOS中常见类型在32位和64位设备上对应的⼤⼩。
C
bool
signedcharunsignedchar
short
unsignedshor吃什么促排卵 tsignedshort
int
unsignedint
signedint
long
unsignedlonglonglong
OC
BOOL(32位)
SInt8、int8_tUInt8、Boolean
int16_t
UInt16SInt16
int32_t、NSInteger(32位)、boolean_t(32位)UInt32(64位)、boolean(64位)NSUInteger(32位)
SInt32(64位)
NSInteger(64位)NSUInteger(64位)
int64_t
32位
1
1
1
2
2
2
4
4
4
4
48
64位
1
1
1
2
2
2
4
4
4
8
88
unsignedlonglongCsignedlonglong
floatdouble
看完了结构体我们再来看看OC中对象的内存结构。
UInt64OC
SInt64
CGFloat(32位)CGFloat(64位)
32位8
8
48
64位8
8
48
@interfacePerson:NSObject
@property(nonatomic,copy)NSString*name;
@property(nonatomic,assign)unsignedshortage;
@property(nonatomic,copy)NSString*nickName;
@property(nonatomic,assign)doubleheight;
@property(nonatomic,assign)charx;
@property(nonatomic,assign)longscore;@end
person的内存结构
可以看到,OC中对象的内存结构跟定义的属性顺序⽆关。第1个8字节是isa指针。
第2个8字节存储的是age和x。
第3个和第4个存储的是name和nickName。
第5个字节存储的是height。第6个字节存储的是score。
由此可得出,OC对对象的内存结构进⾏了
重排
,
减少了内存开销
,使
读取速度变快
。额外补充⼀点⼉,Swift中由于是静态语⾔,所以并不会属性进⾏重排。
swift中对象的内存结构
可以看到Swift中的对象内存结构跟成员变量的顺序有关,所以为了节约内存提⾼读取速度,在Swift定义的模型类成员变量应该从⼤往⼩排列。
在上篇⽂章我们看到alloc底层调⽤了
calloc
开辟了⼀块内存,接下来探究
calloc
的底层。
准备⼯作:
下载malloc源码
编译源码,可参考这⾥
调⽤
calloc并打上断点。
调⽤calloc
通过
Stepinto
进⼊
calloc源码。
calloc
进来是中间层代码,继续跟进。
_malloc_zone_calloc
继续跟进。
default_zone_malloc
在这⼀步创建了真正的zone,然后⽤真正的zone进⾏calloc,继续跟进。
nano_call孔子登山打一字 oc
在这⼀步先计算了总的⼤⼩,然后根据⼤⼩进⼊不同的内存开辟⽅法,这⾥我是传⼊了40,⼩于
NANO_MAX_SIZE
(256),所以进⼊
了
_nano_malloc_check_clear。
_nano_malloc_check_clear
继续跟进
gregate_next_block。
gregate_next_block
如果是第⼀次调⽤
gregated_next_block
函数,
band
不存在,缓存也不会存在,所以会调⽤
gregated_band_grow
来开辟新的
band。
gregated_band_grow
关于通过
nano_blk_addr_t的联合体结构和宏定义如下。
structnano_blk_addr_s{
uint64_t
nano_offt:NANO_OFFSET_BITS,//17locatestheblock
nano_slot:NANO_SLOT_BITS,//4bucketofhomo恰似你的温柔歌词 genousquanta-multipleblocksnano_band:NANO_BAND_BITS,//17
nano_mag_index:NANO_MAG_BITS,//6thecorethatallocatedthisblock
nano_signature:NANOZONE_SIGNATURE_BITS;//theaddressrangedevotedtous.};
#endif
//clang-formatontypedefunion{
uint64_taddr;
structnano_blk_addr_sfields;}nano_blk_addr_t;
#defineSLOT_IN_BAND_SIZE(1<
#defineSLOT_KEY_LIMIT(1<
#defineBAND_SIZE(1<<(NANO_SLOT_BITS+NANO_OFFSET_BITS))/*==Numberofbytescoveredbyapagetableentry*/
#defineNANO_MAG_SIZE(1<
#defineNANO_SLOT_SIZE(1<
#defineNANO_MAG_BITS6
#defineNANO_BAND_BITS17
#defineNANO_SLOT_BITS4责任制度 #defineNANO_OFFSET_BITS17
下⾯来梳理下nanozone分配过程:
确定当前
cpu
对应的
mag
和通过
size
参数计算出来的
slot
,去对应
chained_block_s
的链表中取已经被释放过的内存区块缓存,如果取到检查指针地址是否有问题,没有问题就直接返回;
初次进⾏
nanomalloc
时,
nanozone
并没有缓存,会直接在
nanozone范围的地址空间上直接分配连续地址内存;
如当前
Band
中当前
Slot
耗尽则向系统申请新的
Band
(每个Band固定⼤⼩2M,容纳了16个128k的槽),连续地址分配内存的基地址、limit
地址以及当前分配到的地址由
metadata
结构维护起来,⽽这些
metadata
则以
Mag
、
Slot
为维度(Mag个数是处理器个数,Slot是16个)的⼆维
数组形式,放在
nanozone_t
的
meta_data字段中。
上⾯是当开辟的
size
⼩于
NANO_MAX_SIZE(256)
的情况,接下来探究当
size⼤于256的情况。
szone_calloc
继续跟进。
获取helper_zone
szone_calloc
跟
nano_calloc
⼀样,先根据
num_items
计算
total_bytes,继续跟进。
szone_malloc_should_clear
这⾥以看出在
szone
上分配的内存根据
size
⼤⼩不同包括
tiny
、
small
、
medium
和
large
四⼤类。这⾥我传的
size
是
257
,所以会进⼊
tiny
分
⽀,我们以
tiny为例开始下⾯的分析。
void*
tiny_malloc_should_clear(rack_t*rack,msize_tmsize,boolean_tcleared_requested)
{
void*ptr;
//计算mag_index下标,magazines是⼀个由64个magazine_t组成的数组。
mag_index_tmag_index=tiny_mag_get_thread_index()%rack->num_magazines;
//根据mag_index下标获取magazine。
magazine_t*tiny_mag_ptr=&(rack->magazines[mag_index]);
MALLOC_TRACE(TRACE_tiny_malloc,(uintptr_t)rack,TINY_BYTES_FOR_MSIZE(msize),(uintptr_t)tiny_mag_ptr,cleared_requested);#ifDEBUG_MALLOC
if(DEPOT_MAGAZINE_INDEX==mag_index){
malloc_zone_error(rack->debug_flags,true,"malloccalledformagazineindex-1n");
return(NULL);}
if(!msize){
malloc_zone_error(rack->debug_flags,true,"invariantbroken(!msize)inallocation(region)n");
return(NULL);
}#endif
SZONE_MAGAZINE_PTR_LOCK(tiny_mag_ptr);
//如果开启了tiny的缓存
#ifCONFIG_TINY_CACHE
ptr=tiny_mag_ptr->mag_last_free;
if(tiny_mag_ptr->mag_last_free_msize==msize){
//wehaveawinner
//优先查看上持续英文 次最后释放的区块是否和此托业考试官网 次请求的⼤⼩刚好相等(都是对齐之后的slot⼤⼩),如果是则直接返回。
tiny_mag_ptr->mag_last_free=NULL;
tiny_mag_ptr->mag_last_free_msize=0;
tiny_mag_ptr->mag_last_free_rgn=NULL;
SZONE_MAGAZINE_PTR_UNLOCK(tiny_mag_ptr);
CHECK(szone,__PRETTY_FUNCTION__);if(cleared_requested){
memt(ptr,0,TINY_BYTES_FOR_MSIZE(msize));}
#ifDEBUG_MALLOCif(LOG(szone,ptr)){
malloc_report(ASL_LEVEL_INFO,"intiny_malloc_should_clear(),tinycacheptr=%p,msize=%dn",ptr,msize);
}
#endif
returnptr;}
#endif/*CONFIG_TINY_CACHE*/
//没有开启了tiny的缓存while(1){
//先从freelist查找
ptr=tiny_malloc_from_free_list(rack,tiny_mag_ptr,mag_index,msize);if(ptr){
SZONE_MAGAZINE_PTR_UNLOCK(tiny_mag_ptr);CHECK(szone,__PRETTY_FUNCTION__);
if(cleared_requested){
memt(ptr,0,TINY_BYTES_FOR_MSIZE(msize));}
returnptr;}
#ifCONFIG_RECIRC_DEPOT
//从⼀个后备magazine中取出⼀个可⽤region,完整地拿过来放到当前magazine,再⾛⼀遍上⾯的步骤。
if(tiny_get_region_from_depot(rack,tiny_mag_ptr,mag_index,msize)){
//再次尝试从freelist中获取
ptr=tiny_malloc_from_free_list(rack,tiny_mag_ptr,mag_index,msize);if(ptr){
SZONE_MAGAZINE_PTR_UNLOCK(tiny_mag_ptr);CHECK(szone,__PRETTY_FUNCTION__);
if(cleared_requested){
memt(ptr,0,TINY_BYTES_FOR_MSIZE(msize));}
returnptr;}
}
}
#endif//CONFIG_RECIRC_DEPOT
//gion(heap)mustbeallocatedtosatisfythiscalltomalloc().
//Theallocation,anmmap()systemcall,willbeperformedoutsidethemagazinespinlocksbythefirst
//readts"alloc_underway"andentersacriticalction.
//Threadsarrivingherelaterareexcludedfromthecriticalction,yieldtheCPU,andthenretrythe
//ometimethemagazineisresupplied,theoriginalthreadleaveswithitsallocation,
//(!tiny_mag_ptr->alloc_underway){
//如果没有正在申请新的的regin操作,则进⾏申请操作void*fresh_region;
//timetocreateanewregion(dothisoutsidethemagazinelock)
//设置当前正在申请新的堆
tiny_mag_ptr->alloc_underway=TRUE;OSMemoryBarrier();
SZONE_MAGAZINE_PTR_UNLOCK(tiny_mag_ptr);
//申请新的堆
fresh_region=mvm_allocate_pages(TINY_REGION_SIZE,TINY_BLOCKS_ALIGN,
MALLOC_FIX_GUARD_PAGE_FLA显卡怎么超频 GS(rack->debug_flags),VM_MEMORY_MALLOC_TINY);
SZONE_MAGAZINE_PTR_LOCK(tiny_mag_ptr);//DTraceUSDTProbe
MAGMALLOC_ALLOCREGION(TINY_SZONE_FROM_RACK(rack),(int)mag_index,fresh_region,TINY_REGION_SIZE);if(!fresh_region){//outofmemory!
tiny_mag_ptr->alloc_underway=FALSE;OSMemoryBarrier();
SZONE_MAGAZINE_PTR_UNLOCK(tiny_mag_ptr);
returnNULL;}
region_t_cookie(®ION_COOKIE_FOR_TINY_REGION(fresh_region));
//从最近的⼀个region或者新申请的region中malloc
ptr=tiny_malloc_from_region_no_lock(rack,tiny_mag_ptr,mag_index,msize,fresh_region);
//wedon'tclearbecauthisfreshlyallocatedspaceispristine
tiny_mag_ptr->alloc_underway=FALSE;OSMemoryBarrier();
SZONE_MAGAZINE_PTR_UNLOCK(tiny_mag_ptr);
CHECK(szone,__PRETTY_FUNCTION__);returnptr;
}el{
SZONE_MAGAZINE_PTR_UNLOCK(tiny_mag_ptr);yield();
SZONE_MAGAZINE_PTR_LOCK(tiny_mag_ptr);}
}
/*NOTREACHED*/}
tiny_malloc_from_free_lis
t函数的作⽤是从
free_list中不断进⾏各种策略尝试。
当
free_list
流程仍然找不到可以使⽤内存,就会使⽤
tiny_get_region_from_depot。
每⼀个类型的
rack
指向的
magazines
,都会在下标为-1,
magazine_t
当做备⽤:
depot
,该⽅法的作⽤是从备⽤的
depot
查找出是否有满⾜条件
的
region
如果存在,更新
depot
和
region
的关联关系,然后在关联当前的
magazine_t
和
region,之后在再次重复free_list过程。
scalable_zone分配过程
⾸先检查指针指向地址是否有问题。
如果
lastfree
指针上没有挂载内存区块,则放到
lastfree上。
如果有
lastfree
,置换内存,并把
lastfree
原有内存区块挂载到
freelist
上(在挂载的
freelist
前,会先根据
region
位图检查前后区块是否能合并成更⼤区块,如果能会合并成⼀个)。
合并后所在的region如果空闲字节超过⼀定条件,则将把此
region
放到后备的
magazine中(-1)。
如果整个
region
都是空的,则直接还给系统内核。最后是⼀张⼤神画的流许多英语 程图。
calloc流程图
本文发布于:2023-04-13 18:25:55,感谢您对本站的认可!
本文链接:https://www.wtabcd.cn/fanwen/fan/90/93074.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
留言与评论(共有 0 条评论) |