在线不卡日本ⅴ一区v二区_精品一区二区中文字幕_天堂v在线视频_亚洲五月天婷婷中文网站

  • <menu id="lky3g"></menu>
  • <style id="lky3g"></style>
    <pre id="lky3g"><tt id="lky3g"></tt></pre>

    OC底層原理(二).內存分配與內存對齊

    OC底層原理(二).內存分配與內存對齊

    內存分配開始

    在上一篇的流程圖中,我們看到最后的流程中,在_class_createInstanceFromZone,我們分為三步:

    • 1、size = cls->instanceSize(extraBytes);獲取對象需要分配的內存大小
    • 2、obj = (id)calloc(1, size);如何申請內存
    • 3、obj->initInstanceIsa(cls, hasCxxDtor);初始化isa

    這一篇我們來分析獲取需要分配的大小,以及具體如何分配內存,也就是我們的第1步和第2步。

    分析instanceSize函數的實現

    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());}

    這個方法中的unalignedInstanceSize我們再來看看

    uint32_t unalignedInstanceSize() const { ASSERT(isRealized()); return data()->ro()->instanceSize;}

    我們來分析data()->ro()->instanceSize,data()實現為:

    class_rw_t *data() const { return bits.data();}

    class_ro_t是類在編譯器存儲類信息的數據結構,它里面包含了類的實例變量、方法列表、協議列表等,class_rw_t是用來存儲 在dyld的_map_images方法中將分類的各種信息與class_ro_t合并后的信息。

    data()->ro()->instanceSize也就是獲取對象所有實例變量需要的存儲大小。

    我們再看word_align的實現

    # define WORD_MASK 7ULstatic inline uint32_t word_align(uint32_t x) { return (x + WORD_MASK) & ~WORD_MASK;}

    這個方法先將大小加上7,然后和~WORD_MASK進行邏輯與,由于7的二進制為0000 1111,對它取反后1111 0000,再與前面進行與,那么我們的內存分配的大小就是8的倍數了,也就是8字節(jié)對齊

    最后,if (size < 16) size = 16;這一步就是如果不足16字節(jié),那么我們就補足16,所以一個OC對象,至少是16個字節(jié)。

    整個步驟流程如下:

    instanceSize流程

    獲取內存大小的三種方式

    我們在OC中獲取內存大小的三種方式分別是:

    • sizeof操作符 獲取數據的類型占用空間的大小
    • class_getInstanceSize(Class _Nullable cls) 獲取實例對象中成員變量所需要占用的內存大小
    • malloc_size(const void *ptr) 獲取系統(tǒng)實際分配的內存大小

    我們創(chuàng)建一個LWTestClass來測試

    @interface LWTestClass : NSObject@property (nonatomic, copy) NSString *name;@property (nonatomic, copy) NSString *nickName;@property (nonatomic, assign) int age;@property (nonatomic, assign) long height;@endint main(int argc, const char * argv[]) { @autoreleasepool { LWTestClass *person = [LWTestClass alloc]; person.name = @”Cooci”; person.nickName = @”KC”; NSLog(@”%@ – %lu – %lu – %lu”,person,sizeof([LWTestClass class]),class_getInstanceSize([LWTestClass class]),malloc_size((__bridge const void *)(person))); } return 0;}打印結果: – 8 – 40 – 48

    如上面的結果

    • sizeof中是[LWTestClass class],它是一個指針,我們指針值是8,所以是8
    • class_getInstanceSize中獲取的是LWTestClass類的對象中實例變量所需的大小,我們來分析LWTestClass類結構,isa占8字節(jié),name8字節(jié),nickName8字節(jié),age4字節(jié)補齊到8字節(jié),height8字節(jié),所以LWTestClass類只用40字節(jié)就可以保證存儲了。
    • malloc_size顯示系統(tǒng)分配的內存為48字節(jié),為什么是48字節(jié)呢?我們之前不是看到類的字節(jié)對齊不是8嗎?40也是8的倍數,為什么不是分配40字節(jié)呢?

    為什么是48字節(jié)?

    我們在開篇中所說的第一步是獲取對象所需空間大小,這里我們只能確定對象內存的內存布局是8字節(jié)對齊,這也是class_getInstanceSize獲取到的內存大小,malloc_size獲取到的是系統(tǒng)分配的內存大小,這是屬于我們第二步中obj = (id)calloc(1, size);的內容,calloc方法的分析我們需要用libmalloc源碼,具體的調用順序我們不一一列舉,大致的調用過程如以下流程圖與核心代碼。

    calloc調用流程

    #define SHIFT_NANO_QUANTUM 4#define NANO_REGIME_QUANTA_SIZE (1 SHIFT_NANO_QUANTUM; // round up and shift for number of quanta //再左移4位,這樣得到的大小就是16的倍數 //內存分配也就是以16字節(jié)對齊了 slot_bytes = k << SHIFT_NANO_QUANTUM; // multiply by power of two quanta size *pKey = k – 1; // Zero-based! return slot_bytes;}

    所以,在前面的例子中,我們計算出對象所需的內存字節(jié)大小為40,進行16字節(jié)對齊后,我們實際分配的內存大小為48.

    為什么系統(tǒng)分配內存要以16字節(jié)對齊

    這個沒找到具體的答案,猜想如下:

    • 一個對象是一個struct objc_object的結構體,它至少有一個成員變量isa,一個isa占的大小為8字節(jié)
    • 如果我們以8字節(jié)來分配,兩個連續(xù)的對象在內存中就是連續(xù)存儲在一起的,如果我們以內存偏移(offset)來訪問的話,如果有一定的錯誤,就會訪問到另一個對象的isa中,這樣會造成一定的不安全性。

    這段有想法的大佬希望可以留言給予指導

    內存對齊

    上面我們說內存對齊,那么內存對齊有什么規(guī)則呢?為什么要內存對齊呢?

    內存對齊規(guī)則

    struct/class/union內存對齊原則有四個:

    • 1).數據成員對齊規(guī)則:結構(struct)(或聯合(union))的數據成員,第一個數據成員放在offset為0的地方,以后每個數據成員存儲的起始位置要從該成員大小或者成員的子成員大小(只要該成員有子成員,比如說是數組,結構體等)的整數倍開始(比如int在32位機為4字節(jié), 則要從4的整數倍地址開始存儲),基本類型不包括struct/class/uinon。
    • 2).結構體作為成員:如果一個結構里有某些結構體成員,則結構體成員要從其內部”最寬基本類型成員“的整數倍地址開始存儲.(struct a里存有struct b,b里有char,int ,double等元素,那b應該從8的整數倍開始存儲.)。
    • 3).收尾工作:結構體的總大小,也就是sizeof的結果,.必須是其內部最大成員的”最寬基本類型成員”的整數倍.不足的要補齊.(基本類型不包括struct/class/uinon)。
    • 4).sizeof(union),以結構里面size最大元素為union的size,因為在某一時刻,union只有一個成員真正存儲于該地址。

    內存對齊實踐

    我們設置3個結構體,并分別打印它們所占字節(jié)大小

    struct TestStruct1{ double a; int b; bool c; short d;}st1;struct TestStruct2{ int a; double b; bool c; short d;}st2;struct TestStruct3{ int a; double b; struct TestStruct1 st; bool c; short d;}st3;int main(int argc, const char * argv[]) { NSLog(@”st1:%lu,st2:%lu,st3:%lu”,sizeof(st1),sizeof(st2),sizeof(st3)); //打印結果:st1:16,st2:24,st3:40 return 0;}

    從結果可以看到,st1和st2結構體中所含有的數據類型與個數相同,但位置不同,它們所占的大小也變得不一致了。

    下面我們來分析它們分別的內存構成:

    struct TestStruct1

    • a占8字節(jié),從0開始,它排在內存[0,7]的位置上
    • b占4字節(jié),從8開始,8是4的倍數,符合規(guī)則,所以b存在內存[8,11]位置上
    • c占1字節(jié),從12開始,12是1的倍數,符合規(guī)則,所以c存在內存12位置上
    • d占2字節(jié),從13開始,但是13不是2的倍數,所以往后到14的位置上,它是2的倍數,所以d存在內存[14,15]位置上
    • 所有成員變量排在[0,15]位置上,它的總大小為16,是最大成員變量double(8字節(jié))的倍數,所以struct TestStruct1的總大小為16字節(jié)

    struct TestStruct2

    • a占4字節(jié),從0開始,它排在內存[0,3]的位置上
    • b占8字節(jié),從4開始,但是4不是8的倍數,所以往后順排到8開始排,以b存在內存[8,15]位置上
    • c占1字節(jié),從16開始,16是1的倍數,符合規(guī)則,所以c存在內存16位置上
    • d占2字節(jié),從17開始,但是17不是2的倍數,所以往后到18的位置上,它是2的倍數,所以d存在內存[18,19]位置上
    • 所有成員變量排在[0,19]位置上,它的總大小為20,不是最大成員變量double(8字節(jié))的倍數,我們在后面補0湊夠24變?yōu)?的倍數,所以struct TestStruct2的總大小為24字節(jié)

    struct TestStruct3

    • a占4字節(jié),從0開始,它排在內存[0,3]的位置上
    • b占8字節(jié),從4開始,但是4不是8的倍數,所以往后順排到8開始排,以b存在內存[8,15]位置上
    • st是一個結構體變量,它占16字節(jié),它內部最大的成員變量所占字節(jié)數為8,st從16開始,它是8的倍數,符合規(guī)則,所以st排在內存[16,31]的位置上
    • c占1字節(jié),從32開始,32是1的倍數,符合規(guī)則,所以c存在內存32位置上
    • d占2字節(jié),從33開始,但是33不是2的倍數,所以往后到34的位置上,它是2的倍數,所以d存在內存[34,35]位置上
    • 所有成員變量排在[0,35]位置上,它的總大小為36,不是最大成員變量double和struct TestStruct1(8字節(jié))的倍數,我們在后面補0湊夠40變?yōu)?的倍數,所以struct TestStruct2的總大小為40字節(jié)

    三個結構體的內部布局如下圖

    結構體內存構成

    為什么要內存對齊

    回答這個問題,我們要從內存、CPU以及平臺三個方向來回答

    1.平臺

    移植原因:不是所有的硬件平臺都能訪問任意地址上的任意數據的;某些硬件平臺只能在某些地址處取某些特定類型的數據,否則拋出硬件異常。

    2.CPU原因

    • CPU位數就是CPU的數據總線寬度,而寬度決定了它的單次數據傳送量,比如我們現在iOS平臺一般都是64位,那么64位的CPU一次通電可以傳輸8個字節(jié)的數據,那么我們8個字節(jié)倍數的內存對齊,可以最大化的節(jié)省CPU傳輸的次數。
    • 在CPU眼里,內存是一塊一塊的,塊的大小可以是2、4、8、16字節(jié)大小,因此CPU在讀取內存時是一塊一塊進行讀取的,塊大小稱為memory access granulatity,我們將它翻譯為內存讀取粒度。
      • 假設CPU要讀取一個4字節(jié)的int類型數據到寄存器中,分兩種情況討論: 1)數據從0字節(jié)開始 2)數據從1字節(jié)開始
      • 假設內存讀取粒度為4
      • 當從0字節(jié)開始時,CPU只需要讀取一次即可把這4字節(jié)的數據完全讀取到寄存器中
      • 當該數據是從1字節(jié)開始時,問題變得比較復雜,此時該int類型數據不是位于內存讀取邊界上,那么CPU對它進行讀取,要分為兩次讀取
      • 此時CPU先訪問一次內存,讀取0-3字節(jié)的數據進寄存器,并再次讀取4-5字節(jié)的數據進寄存器,接著把0字節(jié)和6,7,8字節(jié)的數據剔除,最后合并1-4字節(jié)的數據進寄存器。對一個內存未對齊的數據進行了這么多額外的操作,大大降低了CPU性能。
      • 另外,由于平臺原因,有些CPU可能未到邊界即已報異常。

    3.內存原因

    我們先講講內存結構

    • 內存的物理構造中,一個內存是由若干個黑色內存顆粒構成的,每一個內存顆粒叫做一個chip
    • 每一個chip內部,是由8個bank組成的
    • 在每個bank內部,就是電容的行列矩陣結構。(注意,二維矩陣中的一個元素一般存儲著8個bit,也就是說包含了8個小電容)
    • 8個同位置的元素,一起組成在內存中連續(xù)的64個bit
    • 通過內存的物理結構我們可以看出,內存中最小單位就是字節(jié),操作系統(tǒng)在管理它的時候,最小單位也就是字節(jié)。另外,通過上述的我們還有一個額外發(fā)現。那就是在內存中連續(xù)的64個bit,其實在內存的物理結構中,并不連續(xù)。而是分散在同位置的8個rank上的。
    • 內存在進行的時候,一次操作取的就是64bit,所以內存對齊最底層的原因是內存的IO以64bit為單位進行的。對于64位數據寬度的內存,假如cpu也是64位的cpu(現在的計算機基本都是這樣的),每次內存IO獲取數據都是從同行同列的8個chip中各自讀取一個字節(jié)拼起來的。從內存的0地址開始,0-63bit的數據可以一次IO讀取出來,64-127bit的數據也可以一次讀取出來。CPU和內存IO的硬件限制導致沒辦法一次跨在兩個數據寬度中間進行IO。
      • 假如對于一個c的程序員,如果把一個bigint(64位)地址寫到的0x0001開始,而不是0x0000開始,那么數據并沒有存在同一行列地址上。因此cpu必須得讓內存工作兩次才能取到完整的數據。效率自然就很低。
      • 如果不強制對地址進行操作,僅僅只是簡單用c定義一個結構體,編譯和鏈接器會自動替開發(fā)者對齊內存的。盡量幫你保證一個變量不跨列尋址。
      • 在內存硬件層上,還有操作系統(tǒng)層。操作系統(tǒng)還管理了CPU的一級、二級、三級緩存。實際中不一定每次IO都從內存出,如果你的數據局部性足夠好,那么很有可能只需要少量的內存IO,大部分都是更為高效的高速緩存IO。但是高速緩存和內存一樣,也是要考慮對齊的。

    作者:默默_David鏈接:https://www.jianshu.com/p/d285a33bf88f來源:簡書著作權歸作者所有。商業(yè)轉載請聯系作者獲得授權,非商業(yè)轉載請注明出處。

    鄭重聲明:本文內容及圖片均整理自互聯網,不代表本站立場,版權歸原作者所有,如有侵權請聯系管理員(admin#wlmqw.com)刪除。
    用戶投稿
    上一篇 2022年7月3日 15:09
    下一篇 2022年7月3日 15:10

    相關推薦

    • 筆記本最好配置(目前筆記本最好的配置)

      本文主要講的是筆記本最好配置,以及和目前筆記本最好的配置相關的知識,如果覺得本文對您有所幫助,不要忘了將本文分享給朋友。 筆記本電腦什么配置好? 01 CPU:這個主要取決于頻率和…

      2022年11月26日
    • 存儲過程語法(sql server存儲過程語法)

      今天小編給各位分享存儲過程語法的知識,其中也會對sql server存儲過程語法進行解釋,如果能碰巧解決你現在面臨的問題,別忘了關注本站,現在開始吧! oracle存儲過程基本語法…

      2022年11月26日
    • 《光遇》11月25日紅石在哪里 11.25紅石位置

      光遇11月25日的紅石出現在霞谷圓夢村,許多小伙伴都還不知道它具體在哪,下面就讓小編來給大家介紹一下光遇11.25紅石的位置,感興趣的小伙伴快來看看吧。 光遇11.25紅石位置 1…

      2022年11月25日
    • 《光遇》11月25日季節(jié)蠟燭在哪 11.25季節(jié)蠟燭位置2022

      光遇季節(jié)蠟燭的位置每天都會變化,今天出現在了雨林地區(qū),下面小編就給大家?guī)砹斯庥?1.25季節(jié)蠟燭位置分享,有需要的小伙伴不要錯過哦。 光遇11.25季節(jié)蠟燭位置2022 今日季節(jié)…

      2022年11月25日
    • 什么是推廣cpa一篇文章帶你看懂CPA推廣渠道

      CPA渠道 CPA指的是按照指定的行為結算,可以是搜索,可以是注冊,可以是激活,可以是搜索下載激活,可以是綁卡,實名認證,可以是付費,可以是瀏覽等等。甲乙雙方可以根據自己的情況來定…

      2022年11月25日
    • 抖音直播帶貨有哪些方法技巧(抖音直播帶貨有哪些痛點)

      如今抖音這個短視頻的變現能力越來越突顯了,尤其是在平臺上開通直播,更具有超強的帶貨屬性,已經有越來越多的普通人加入到其中了。不過直播帶貨雖然很火,但是也不是每個人都能做好的,那么在…

      2022年11月24日
    • 《寶可夢朱紫》樁子是什么?二級神封印樁位置一覽

      寶可夢朱紫中有一種叫做二級神封印樁的特殊收集道具,很多玩家不知道寶可夢朱紫樁子是什么,下面就帶來寶可夢朱紫二級神封印樁位置一覽,感興趣的小伙伴不要錯過,希望能幫助到大家。 二級神封…

      2022年11月24日
    • 《寶可夢朱紫》太晶水地龍捕捉位置一覽 太晶水地龍在哪里捕捉

      近日在貼吧看到有許多玩家在寶可夢朱紫中遇到了《寶可夢朱紫》太晶水地龍捕捉位置一覽的問題,又不知道該怎么辦。今天在這里,小編為大家?guī)淼木褪沁@個問題的解方案,只要你跟著小編的節(jié)奏來,…

      2022年11月24日
    • 園屬于什么結構(園的結構和部首)

      園 yuán:全包圍結構,平穩(wěn)端正中稍帶左收右展。 外部“口” 體態(tài)端莊,稍抗肩,稍帶左輕右重。左豎起筆稍抖,豎身勿重,稍左斜,垂露收筆;第二筆橫折壓著左豎起筆,橫畫稍抗肩,不要重…

      2022年11月24日
    • 明查|美國新冠后遺癥患者中有16%癥狀嚴重以致無法工作?

      點擊進入澎湃新聞全球事實核查平臺 速覽 – 網傳數據比例無權威信源佐證,該比例有可能是結合了美國疾病防控中心和布魯金斯學會的數據得出,但這兩個機構的調研目的和樣本都不同…

      2022年11月24日

    聯系我們

    聯系郵箱:admin#wlmqw.com
    工作時間:周一至周五,10:30-18:30,節(jié)假日休息