iOS底層之內(nèi)存對(duì)齊算法解析

目前但凡一個(gè)iOS崗面試都會(huì)問(wèn)個(gè)內(nèi)存對(duì)齊問(wèn)題,那么什么是字節(jié)對(duì)齊魂务?成員變量對(duì)齊和對(duì)象內(nèi)存對(duì)齊有什么區(qū)別?今天我來(lái)為大家一一解析

iOS創(chuàng)建對(duì)象的_class_createInstanceFromZone方法中會(huì)通過(guò)instanceSize方法計(jì)算創(chuàng)建對(duì)象需要開辟的內(nèi)存空間(細(xì)創(chuàng)建對(duì)象流程點(diǎn)我)洞焙,源碼如下:

inline size_t instanceSize(size_t extraBytes) const {
        if (fastpath(cache.hasFastInstanceSize(extraBytes))) {
            return cache.fastInstanceSize(extraBytes);
        }

        size_t size = alignedInstanceSize() + extraBytes;
        // CF requires all objects be at least 16 bytes.
        if (size < 16) size = 16;
        return size;
    }

alignedInstanceSize()源碼如下:

uint32_t alignedInstanceSize() const {
        return word_align(unalignedInstanceSize());
    }

通過(guò)分析源碼得出以下結(jié)論:

  1. 對(duì)象開辟的空間大小完全取決于對(duì)象的成員變量(depending on class's ivars)猾愿,通過(guò)成員變量得出未對(duì)齊的內(nèi)存大小:
// May be unaligned depending on class's ivars.
uint32_t unalignedInstanceSize() const {
        ASSERT(isRealized());
        return data()->ro()->instanceSize;
    }
  1. 若對(duì)象沒(méi)有成員變量他巨,則屬性只有從基類NSObject繼承來(lái)的isa展哭,isa為一個(gè)class類型的結(jié)構(gòu)體指針Class isa OBJC_ISA_AVAILABILITY;,因此一個(gè)對(duì)象最小的未對(duì)齊空間unalignedInstanceSize為8闻蛀。

  2. 得到unalignedInstanceSize以后匪傍,通過(guò)word_align方法將其字節(jié)對(duì)齊,接下來(lái)逐步分析具體成員變量對(duì)齊方法

  • 對(duì)齊方法代碼:
static inline uint32_t word_align(uint32_t x) {
    return (x + WORD_MASK) & ~WORD_MASK;
}

其中WORD_MASK定義為define WORD_MASK 7UL

  • 假設(shè)當(dāng)前對(duì)象沒(méi)有成員變量觉痛,則x = 8役衡,以上表達(dá)式可轉(zhuǎn)化為:
   (8 + 7) & ~7
->  15 & ~7
  • 轉(zhuǎn)為二進(jìn)制則為:
15      : 0000 1111
7       : 0000 0111 
~7      : 1111 1000
15 & ~7 : 0000 1000 = 8

一個(gè)無(wú)成員變量的對(duì)象通過(guò)對(duì)齊得到的alignedInstanceSize為8。

  • 字節(jié)對(duì)齊算法的意義為8字節(jié)對(duì)齊薪棒,取8的整數(shù)倍手蝎。目的在于在計(jì)算讀取成員變量的時(shí)候,如果成員變量根據(jù)實(shí)際類型設(shè)定大小俐芯,會(huì)影響計(jì)算機(jī)讀取速度棵介,但如果統(tǒng)一以8字節(jié)為度量衡,可以加快讀取速度(以空間換取時(shí)間)這個(gè)算法也可以用以下方式表達(dá):
  (8 + 7) >> 3 << 3           右移3位再左移三位
  15 : 0000 1111
  右移三位 -> 0000 0001
  左移三位 -> 0000 1000 = 8
  • 得到alignedInstanceSize后吧史,instanceSize最后會(huì)通過(guò)if (size < 16) size = 16條件將小于16的結(jié)果全部轉(zhuǎn)為16邮辽,至此,一個(gè)無(wú)成員變量的對(duì)象開辟的空間為16贸营。

4.接下來(lái)分析對(duì)象帶有多個(gè)成員變量的情況:

@property (nonatomic,copy)NSString *name;

@property (nonatomic,assign)int age;

@property (nonatomic,assign)long height;

@property (nonatomic,copy)NSString *nickName;

@property (nonatomic,assign)BOOL isMale;

并進(jìn)行賦值:

p.name = @"FC";
p.age = 18;
p.height = 185;
p.nickName = @"XX";
p.isMale = YES;

在控制臺(tái)通過(guò)x/6gx p 來(lái)打印對(duì)象p的詳細(xì)內(nèi)存情況:


image.png

其中第一段內(nèi)存為對(duì)象的isa吨述,第二段內(nèi)存中,系統(tǒng)將屬性age和屬性isMale兩個(gè)4字節(jié)的屬性進(jìn)行了組合钞脂,優(yōu)化了內(nèi)存分配揣云,打印結(jié)果如圖:

image.png

5.內(nèi)存對(duì)齊原則:

  • 數(shù)據(jù)成員對(duì)?規(guī)則:結(jié)構(gòu)(struct)(或聯(lián)合(union))的數(shù)據(jù)成員,第?個(gè)數(shù)據(jù)成員放在offset為0的地?冰啃,以后每個(gè)數(shù)據(jù)成員存儲(chǔ)的起始位置要
    從該成員??或者成員的?成員??(只要該成員有?成員邓夕,?如說(shuō)是數(shù)組,結(jié)構(gòu)體等)的整數(shù)倍開始(?如int為4字節(jié),則要從4的整數(shù)倍地址開始存儲(chǔ)阎毅。

  • 結(jié)構(gòu)體作為成員:如果?個(gè)結(jié)構(gòu)?有某些結(jié)構(gòu)體成員,則結(jié)構(gòu)體成員要從
    其內(nèi)部最?元素??的整數(shù)倍地址開始存儲(chǔ).(struct a?存有struct b,b
    ?有char,int ,double等元素,那b應(yīng)該從8的整數(shù)倍開始存儲(chǔ).)

  • 收尾?作:結(jié)構(gòu)體的總??,也就是sizeof的結(jié)果,.必須是其內(nèi)部最?
    成員的整數(shù)倍.不?的要補(bǔ)?焚刚。

舉例:


image.png

以上兩個(gè)結(jié)構(gòu)體內(nèi)存大小是否一致?答案:不一致净薛,分別為24和16汪榔。
為什么同樣的成員變量,因?yàn)轫樞虿煌瑑?nèi)存結(jié)果不同?
依據(jù)原則1”每個(gè)成員變量?jī)?chǔ)存的起始位置需要可以整除該成員變量的字節(jié)大小“痴腌,得出兩個(gè)結(jié)構(gòu)體的儲(chǔ)存流程如下:


image.png

image.png

再依據(jù)原則3雌团,內(nèi)存結(jié)果必須為最大成員的整數(shù)倍,得出結(jié)果為24和16士聪。

為什么每個(gè)成員變量起始位置必須要是自己的整數(shù)倍呢锦援?


image.png

不做對(duì)齊操作時(shí),計(jì)算機(jī)讀取此結(jié)構(gòu)體變量需要讀取4次剥悟,每次讀取大小分別為8灵寺,1,2区岗,4略板,讀取次數(shù)多,每次讀取次數(shù)不同慈缔,降低讀取效率叮称。

image.png

內(nèi)存對(duì)齊操作后,可以看到后面3個(gè)成員變量組合到了一起藐鹤,并且系統(tǒng)會(huì)告知該組合中含有大小為1瓤檐,2,4的成員變量娱节,計(jì)算機(jī)再讀取此結(jié)構(gòu)體的時(shí)候只需以8字節(jié)為單位讀取兩次挠蛉,大大提高了讀取效率,這就是需要內(nèi)存對(duì)齊的原因啦

6.對(duì)象內(nèi)存最終結(jié)果

  • 創(chuàng)建一個(gè)類:


    image.png
  • 如圖賦值并進(jìn)行打右蘼:


    image.png
  • 打印結(jié)果為:


    image.png

按照之前的分析:
sizeof(p)結(jié)果為8(p為結(jié)構(gòu)體指針谴古,大小為8);
class_getInstanceSize([FCPerson class])為成員變量大小+isa悄窃,結(jié)果為40
malloc_size是什么讥电?為什么等于48?

通過(guò)研究malloc源碼轧抗, 在calloc方法最后調(diào)用的segregated_size_to_fit方法中:

static MALLOC_INLINE size_t
segregated_size_to_fit(nanozone_t *nanozone, size_t size, size_t *pKey)
{
    size_t k, slot_bytes;

    if (0 == size) {
        size = NANO_REGIME_QUANTA_SIZE; // Historical behavior
    }
    k = (size + NANO_REGIME_QUANTA_SIZE - 1) >> SHIFT_NANO_QUANTUM; // round up and shift for number of quanta
    slot_bytes = k << SHIFT_NANO_QUANTUM;                           // multiply by power of two quanta size
    *pKey = k - 1;                                                  // Zero-based!

    return slot_bytes;
}

size為傳入的40,NANO_REGIME_QUANTA_SIZE為16瞬测, SHIFT_NANO_QUANTUM為4横媚。
slot_bytes就是返回的最終的對(duì)象大小,其算法為: (40 + 16 - 1) >> 4 << 4月趟,即對(duì)40進(jìn)行16字節(jié)對(duì)齊灯蝴,最終結(jié)果為48。

16字節(jié)對(duì)齊的原因:若以8字節(jié)對(duì)齊孝宗,64字節(jié)以內(nèi)穷躁,可以存放8個(gè)8字節(jié)對(duì)象(8,16因妇,24问潭,32猿诸,40,48狡忙,56梳虽,64),對(duì)象之間兩兩相鄰灾茁。若以16字節(jié)對(duì)齊窜觉,64字節(jié)內(nèi)可以放4個(gè)對(duì)象(16,32北专,48禀挫,64),對(duì)象兩兩相鄰的次數(shù)降低拓颓,減少系統(tǒng)誤指的概率特咆。另一方面,一個(gè)對(duì)象的成員變量最小為8(isa)录粱,但只含有默認(rèn)變量isa的對(duì)象概率極低腻格,實(shí)際使用中絕大部分類都有額外的成員變量,若以8字節(jié)對(duì)齊則每個(gè)對(duì)象內(nèi)存都要做額外計(jì)算啥繁,因此直接使用16字節(jié)對(duì)齊會(huì)提高效率菜职。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市旗闽,隨后出現(xiàn)的幾起案子酬核,更是在濱河造成了極大的恐慌,老刑警劉巖适室,帶你破解...
    沈念sama閱讀 218,451評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件嫡意,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡捣辆,警方通過(guò)查閱死者的電腦和手機(jī)蔬螟,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,172評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)汽畴,“玉大人旧巾,你說(shuō)我怎么就攤上這事∪绦” “怎么了鲁猩?”我有些...
    開封第一講書人閱讀 164,782評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)罢坝。 經(jīng)常有香客問(wèn)我廓握,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,709評(píng)論 1 294
  • 正文 為了忘掉前任隙券,我火速辦了婚禮男应,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘是尔。我一直安慰自己殉了,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,733評(píng)論 6 392
  • 文/花漫 我一把揭開白布拟枚。 她就那樣靜靜地躺著薪铜,像睡著了一般。 火紅的嫁衣襯著肌膚如雪恩溅。 梳的紋絲不亂的頭發(fā)上隔箍,一...
    開封第一講書人閱讀 51,578評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音脚乡,去河邊找鬼蜒滩。 笑死,一個(gè)胖子當(dāng)著我的面吹牛奶稠,可吹牛的內(nèi)容都是我干的俯艰。 我是一名探鬼主播,決...
    沈念sama閱讀 40,320評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼锌订,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼竹握!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起辆飘,我...
    開封第一講書人閱讀 39,241評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤啦辐,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后蜈项,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體芹关,經(jīng)...
    沈念sama閱讀 45,686評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,878評(píng)論 3 336
  • 正文 我和宋清朗相戀三年紧卒,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了侥衬。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,992評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡常侦,死狀恐怖浇冰,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情聋亡,我是刑警寧澤,帶...
    沈念sama閱讀 35,715評(píng)論 5 346
  • 正文 年R本政府宣布际乘,位于F島的核電站坡倔,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜罪塔,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,336評(píng)論 3 330
  • 文/蒙蒙 一投蝉、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧征堪,春花似錦瘩缆、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,912評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至谐算,卻和暖如春熟尉,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背洲脂。 一陣腳步聲響...
    開封第一講書人閱讀 33,040評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工斤儿, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人恐锦。 一個(gè)月前我還...
    沈念sama閱讀 48,173評(píng)論 3 370
  • 正文 我出身青樓往果,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親一铅。 傳聞我的和親對(duì)象是個(gè)殘疾皇子陕贮,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,947評(píng)論 2 355

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