swift之內(nèi)存布局

struct和tuple內(nèi)存布局

結(jié)構(gòu)體和元組當(dāng)前共享相同的布局算法,在編譯器實(shí)現(xiàn)中稱為“通用”布局算法。算法如下:

  • 一開始設(shè)置size為0,alignment為1

  • 遍歷字段,對于每個(gè)字段:

    • 先根據(jù)這個(gè)字段的alignment來更新size,讓這個(gè)字段能夠?qū)R

    • size增加這個(gè)字段的大小

    • 更新alignment為 Max(alignment,這個(gè)字段的alignment)

  • 最終拿到總的size和alignment,然后size根據(jù)alignment對其,得到strip。

比如:

structPerson{
   var age:Int64=0
   var sex:UInt16=0
   var address:Double=0.0
   var name:UInt8=0
}

開始設(shè)置size=0,alignment=1
age是8字節(jié)對其,size為0,沒問題,不需要調(diào)整。
age是8字節(jié),size增加8
age的alignment為8,更新alignment為Max(1,8)
sex為2字節(jié)對其,size為8,沒問題,不用調(diào)整。
sex是2字節(jié),size增加2,為10
sex的alignment為2,更新alignment為Max(8,2)
address為8字節(jié)對其,size為10,需調(diào)整size為16,來保證對其。
address是8字節(jié),size增加8,為24
address的alignment為8,更新alignment為Max(8,8)
name為1字節(jié)對其,size為24,不用調(diào)整。
name是1字節(jié),size增加1,為25
name的alignment為1,更新alignment為Max(8,1)
所以size為25, alignment為8, 調(diào)整strip為32,保證對其。</pre>

Class Layout

參考https://academy.realm.io/posts/goto-mike-ash-exploring-swift-memory-layout/, 里面有一個(gè)探索內(nèi)存的工具https://github.com/mikeash/memorydumper2。我們傳一個(gè)變量給他,他能分析出這個(gè)是一個(gè)指針還是其他東西,并給出關(guān)系圖譜,不過需要安裝Graphviz。

class是引用類型,因此,我們定義一個(gè)變量拿到的是這個(gè)實(shí)例變量在內(nèi)存中的引用。

classPerson{
   var age=0x0101010101010101
   var money= 0x0202020202020202
}
letp=Person()

那么p指向的實(shí)例對象的內(nèi)存模式大概長什么樣呢?

image

現(xiàn)在還看不懂,我們先介紹下swift源碼中的一些數(shù)據(jù)結(jié)構(gòu)。

HeapObject

swift中所有分配在堆上的東西都是一個(gè)HeapObject。我們看看HeapObject的定義:

/** 我們看到其實(shí)就兩個(gè)變量,一個(gè)metadata,和一個(gè)InlineRefCounts */
struct HeapObject{
 /// This is always a valid pointer to a metadata object.
 HeapMetadataconst*metadata;
?
 InlineRefCounts refCounts;
};

HeapMetadata

HeapMetadata是類結(jié)構(gòu)體的指針。

//===============HeapMetadata=============
template<typenameTarget>structTargetHeapMetadata;
usingHeapMetadata=TargetHeapMetadata<InProcess>;
//================TargetHeapMetadata==============
//繼承自TargetMetadata,并且有兩個(gè)初始化方法,通過Kind初始化和通過TargetAnyClassMetadata初始化
template<typenameRuntime>
structTargetHeapMetadata: TargetMetadata<Runtime>{
 usingHeaderType=TargetHeapMetadataHeader<Runtime>;
?
 TargetHeapMetadata() =default;
 constexprTargetHeapMetadata(MetadataKindkind)
  : TargetMetadata<Runtime>(kind) {}
#if SWIFT_OBJC_INTEROP
 constexprTargetHeapMetadata(TargetAnyClassMetadata<Runtime>*isa)
  : TargetMetadata<Runtime>(isa) {}
#endif
};
//=================Kind==========
enumclassMetadataKind: uint32_t{
#define METADATAKIND(name, value) name = value,
#define ABSTRACTMETADATAKIND(name, start, end)                                 \
 name##_Start = start, name##_End = end,
#include "MetadataKind.def"
   /** 這是從MetadataKind.def截取的一段,可以看到就是聲明了各種Kind的枚舉
NOMINALTYPEMETADATAKIND(Struct, 1)
NOMINALTYPEMETADATAKIND(Enum, 2)
NOMINALTYPEMETADATAKIND(Optional, 3)
METADATAKIND(Opaque, 8)
METADATAKIND(Tuple, 9)
METADATAKIND(Function, 10)
METADATAKIND(Existential, 12)
METADATAKIND(Metatype, 13)
METADATAKIND(ObjCClassWrapper, 14)*/
 LastEnumerated=2047,
};
//================TargetAnyClassMetadata==========
//
template<typenameRuntime>
structTargetAnyClassMetadata: publicTargetHeapMetadata<Runtime>{
....
 ConstTargetMetadataPointer<Runtime, swift::TargetClassMetadata>Superclass;
 TargetPointer<Runtime, void>CacheData[2];
 StoredSizeData;
.....
};
structTargetHeapMetadata: TargetMetadata<Runtime>{
....
};
structTargetMetadata{
   //StoredPointer是不同平臺(tái)上指針大小的類型,32位上相當(dāng)于int32,64位上為int64,他要么是一個(gè)MetaKind枚舉,要么是一個(gè)isa指針。
StoredPointerKind;
}
綜上我們再看看TargetAnyClassMetadata都有些什么:
{
 StoredPointerKind;
 ConstTargetMetadataPointer<Runtime, swift::TargetClassMetadata>Superclass;
 TargetPointer<Runtime, void>CacheData[2];
 StoredSizeData;
}
熟悉么?沒什么感覺的話,我們再看看oc的class的定義:
structobjc_class{
   ClassISA;//對應(yīng)Kind
   Classsuperclass;//對應(yīng)Superclass
   cache_tcache;   //對應(yīng)cachedata
   class_data_bits_tbits; //對應(yīng)Data
}

綜上,我們看到HeapMetadata就是一個(gè)類結(jié)構(gòu)體。代表這個(gè)實(shí)例的Class。HeapObject中的第一個(gè)變量HeapMetadata const *metadata就是一個(gè)指向Class對象的指針。

InlineRefCounts

typedef RefCounts<InlineRefCountBits>InlineRefCounts;
?
template<typenameRefCountBits>
classRefCounts{
 std::atomic<RefCountBits>refCounts;
 /** 各種操作引用計(jì)數(shù)的方法 */
 incrementSlow()
 tryIncrementAndPinSlow
 decrementShouldDeinit
.....
}

從上可以看出InlineRefCounts就是InlineRefCountBits,并提供了各種操作引用計(jì)數(shù)的方法。

typedef RefCountBitsT<RefCountIsInline>InlineRefCountBits;
?
template<RefCountInlinednessrefcountIsInline>
classRefCountBitsT{
 //可能包含的是引用計(jì)數(shù),也可能包含的是sidetable的指針
 BitsTypebits;
 RefCountBitsT(HeapObjectSideTableEntry*side)
 RefCountBitsT(uint32_tstrongExtraCount, uint32_tunownedCount);
 //封裝了對各個(gè)bit的存儲(chǔ)的操作
 getUnownedRefCount
 getIsPinned
 getIsDeiniting
 。。。。。
}

從上可以看到,refCounts是一個(gè)RefCountBitsT類,類中有一個(gè)bits。如果沒有sidetable的情況,那么引用計(jì)數(shù)會(huì)記錄在這里面,如果有sidetable,那么包含的是HeapObjectSideTableEntry的指針。

//看看每一位表示什么(64位情況)
template<>
structRefCountBitOffsets<8>{
 static const size_t IsPinnedShift=0;
 static const size_tIsPinnedBitCount=1;
 static const uint64_tIsPinnedMask=maskForField(IsPinned);
?
 static const size_tUnownedRefCountShift=shiftAfterField(IsPinned);
 static const size_tUnownedRefCountBitCount=31;
 static const uint64_tUnownedRefCountMask=maskForField(UnownedRefCount);
?
 static const size_tIsDeinitingShift=shiftAfterField(UnownedRefCount);
 static const size_tIsDeinitingBitCount=1;
 static const uint64_tIsDeinitingMask=maskForField(IsDeiniting);
?
 static const size_tStrongExtraRefCountShift=shiftAfterField(IsDeiniting);
 static const size_tStrongExtraRefCountBitCount=30;
 static const uint64_tStrongExtraRefCountMask=maskForField(StrongExtraRefCount);
?
 static const size_tUseSlowRCShift=shiftAfterField(StrongExtraRefCount);
 static const size_tUseSlowRCBitCount=1;
 static const uint64_tUseSlowRCMask=maskForField(UseSlowRC);
?
 static const size_tSideTableShift=0;
 static const size_tSideTableBitCount=62;
 static const uint64_tSideTableMask=maskForField(SideTable);
 static const size_tSideTableUnusedLowBits=3;
?
 static const size_tSideTableMarkShift=SideTableBitCount;
 static const size_tSideTableMarkBitCount=1;
 static const uint64_tSideTableMarkMask=maskForField(SideTableMark);
};
===============================
低位---》高位
1位         IsPinned
31位        UnownedRefCountBit
1位         IsDeiniting
30位        StrongExtraRefCount
1位         UseSlowRC
如果是有sidetable的情況,各個(gè)bit表示如下:
62位        SideTable指針
1位         SideTableMark
1位         UseSlowRC

再看對象內(nèi)存布局

再看看一開始的圖:

image

E87b5600100000:是Class的地址。

02000000000000:是inlineref。引用計(jì)數(shù),不過怎么都和看到的offset對不上。02那個(gè)是00000010,其中的1是代表unowned ref的數(shù)量,我們可以驗(yàn)證下:

class Person{
   var age=0x0102030405060708
   var money= 0x0202020202020202
   func sayHello() {
       print("hello")
   }
   func sayWorld() {
       print("world")
   }
}
let p1=Person()
let p2=p1
let p3=p1
let p4=p1
let p5=p1
unowned var p6=p1
unowned var p7=p1
dumpAndOpenGraph(dumping:p1,maxDepth:10,filename:"SimpleClassPerson")

上面有5個(gè)額外強(qiáng)引用,2個(gè)額外無主引用(總數(shù)為2+1,因?yàn)槌跏贾禐?),雖然不知道具體的offset規(guī)則,不過我們可以肯定,一定有一個(gè)1010和一個(gè)11子序列,代表5個(gè)額外強(qiáng)引用和3個(gè)無主引用:

image

可以看到06(00000110),其中11位無主引用數(shù)3,0a(00001010),其中101為額外強(qiáng)引用計(jì)數(shù)。

ok,我們在試試定義一個(gè)weak,讓對象有sidetable(因?yàn)閣eak引用計(jì)數(shù)在sidetable的SideTableRefCountBits中)。

weak var p8=p1
image

可以看到ref已經(jīng)變味一個(gè)指針+flag。

Class的layout

image

這是Person這個(gè)Class中的信息,讀取的信息描述也不是很清晰,我們大概可以認(rèn)為他是一個(gè)特殊的vtable,混雜了一些oc類結(jié)構(gòu)體中的一些信息。

Class{
   isa
   supperclass
   cache
  各種method指針
}
image

這是放大的部分,128字節(jié)開始是sayHello函數(shù),136字節(jié)開始是sayWorld函數(shù),還有各種getter和setter等。

image

image

第8字節(jié)開始,也就數(shù)superclass,可以看到,我們定義的Person是SwiftObject的子類。SwiftObject是一個(gè)實(shí)現(xiàn)了NSObject協(xié)議的OC類。

枚舉的內(nèi)存布局

在layout枚舉時(shí),ABI為了避免浪費(fèi)空間,會(huì)從以下的5種策略中選擇。

Empty Enums

enum Empty{}// => empty type
此時(shí)size為0, alignment為1, 為了對齊strip為1

Single-Case Enums

如果enum只有一個(gè)case,那么關(guān)聯(lián)了什么data就怎么布局,如果沒有關(guān)聯(lián),那么為empty(因?yàn)橹挥幸粋€(gè)case不需要區(qū)分)。

enum EmptyCase{caseX}            // => empty type
enum DataCase{caseY(Int,Double)}// => LLVM <{ i64, double }>
EmptyCasesize為0,alignment為1,strip為1
DataCase布局就是1個(gè)Int,Double。所以size為16,alignment為8,strip為16

C-Like Enums

如果所有case都沒有關(guān)聯(lián)data type,那么這就是一個(gè)c-like enum。enum布局就是一個(gè)整數(shù)tag,用最少的bit來描述所有case。

enum EnumLike2{// => LLVM i1
 case A        // => i1 0
 case B        // => i1 1
}
?
enum EnumLike8{// => LLVM i3
 case A        // => i3 0
 case B        // => i3 1
 case C        // => i3 2
 case D        // etc.
 case E
 case F
 case G
 case H
}
size為1, alignment為1,strip為1。為什么是1呢,因?yàn)?個(gè)字節(jié)可以表示2的8次方種情況,足夠表示這么多case了。那超過了呢?
又比如
enum A{
   case A0
   case A1
   ....
   case A280
}
此時(shí)不足以用1個(gè)字節(jié)表示所以,size為2,alignment為2,strip為2.

Single-Payload Enums

如果enum總有多個(gè)case,但是只有一個(gè)關(guān)聯(lián)了data type,其他都沒有,我們稱這種情況為single-payload enum。此時(shí)的原則就是盡量共用空間,無法共用時(shí),增加額外的位來區(qū)分情況。

32位足夠描述所有情況,就用這么多
enum CharOrSectionMarker{//=> LLVM i32
 case Paragraph          // => i32 0x0020_0000
 case Char(UnicodeScalar)// => i32 (zext i21 %Char to i32)
 case Chapter            // => i32 0x0020_0001
}
?
CharOrSectionMarker.Char('\x00')=>i320x0000_0000
CharOrSectionMarker.Char('\u10FFFF')=>i320x0010_FFFF
?
enum CharOrSectionMarkerOrFootnoteMarker{=>LLVMi32
 case CharOrSectionMarker(CharOrSectionMarker)=>i32%CharOrSectionMarker
 case Asterisk                                =>i320x0020_0002
 case Dagger                                  =>i320x0020_0003
 case DoubleDagger                            =>i320x0020_0004
}
//不夠了,總的情況為Int描述的+2,超出了Int8字節(jié)能表示的訪問,就在末尾增加一個(gè)字節(jié)來區(qū)分就是是有data還是沒data。
enum IntOrInfinity{=>LLVM<{i64,i1增加一位區(qū)分有沒datatype}>
 case NegInfinity   =><{i64,i1}>{   0,1}
 case Int(Int)      =><{i64,i1}>{%Int,0}
 case PosInfinity   =><{i64,i1}>{   1,1}
}
?
IntOrInfinity.Int(   0)=><{i64,i1}>{    0,0}
IntOrInfinity.Int(20721)=><{i64,i1}>{20721,0}
size為9,alignment為8,strip為16

Multi-Payload Enums

如果有大于1個(gè)case關(guān)聯(lián)了data type,那么就是Multi-Payload Enums。此時(shí)也是一樣的盡量共用空間,無法共用時(shí),增加bit進(jìn)行區(qū)分。

class Bignum{}
?
enum IntDoubleOrBignum{=>LLVM<{i64,i2增加兩位區(qū)分3中情況 }>
 case Int(Int)          =><{i64,i2}>{          %Int,           0}
 case Double(Double)    =><{i64,i2}>{(bitcast %Doubletoi64),1}
 case Bignum(Bignum)    =><{i64,i2}>{(ptrtoint%Bignumtoi64),2}
}
size為9,alignment為8,strip為16

Existential Container Layout

protocol類型、組合協(xié)議類型、Any等這些無法確定大小的類型,他們都Existential Container。具有同樣的layout。

protocol

Existential Containers必須容納任意大小和對齊的值。此時(shí)使用3個(gè)指針大小的固定數(shù)據(jù)區(qū)。如果他的大小和對其都小于等于固定緩沖區(qū)的大小,則直接包含該值。如果不能包含,這存儲(chǔ)一個(gè)指向其數(shù)據(jù)的指針。具體是什么類型,由一個(gè)類型元數(shù)據(jù)記錄標(biāo)識(shí)。protocol的方法在witnesstable中。

struct OpaqueExistentialContainer{
 void*fixedSizeBuffer[3];//數(shù)據(jù)區(qū)
 Metadata*type;//數(shù)據(jù)區(qū)的類型
 WitnessTable*witnessTables[NUM_WITNESS_TABLES];//protocol的witnesstable
};
struct StructSmallP:P{
   func f(){}
   func g(){}
   func h(){}
   vara=0x6c6c616d73
   varb=0x6c6c616d73
}
letp=StructSmallP()
dumpAndOpenGraph(dumping:p,maxDepth:4,filename:"StructSmallP")
image

就兩個(gè)Int的空間。ok,現(xiàn)在我們用Existential Container來存儲(chǔ)他:

let p:P=StructSmallP()//定義為P
dumpAndOpenGraph(dumping:p,maxDepth:4,filename:"StructSmallP")
image
image

前兩個(gè)為struct的a和b。前3個(gè)緩沖區(qū)剩余空間會(huì)用于存放valuewitnesstable中的一些方法,包含一些初始化函數(shù),如果沒空間了就沒有這個(gè)。第四個(gè)為type,第5個(gè)為protocol witnesstable。正如我們最開始看到的那樣:3個(gè)緩沖區(qū),一個(gè)type,一個(gè)pwt。

AnyObject

AnyObject由于對象都是指針,所以不會(huì)存在大小不一致的情況。

let objs=[ClassA(),ClassB(),ClassC()]

Any

和protocol類似,只是沒有了protocol witness table。

struct OpaqueExistentialContainer{
 void*fixedSizeBuffer[3];//數(shù)據(jù)區(qū)
 Metadata*type;//數(shù)據(jù)區(qū)的類型
};
image
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容