三、Tagged Pointer對象

1鲸郊、原有系統(tǒng)的問題

假設我們要存儲一個NSNumber對象丰榴,其值是一個整數(shù),正常情況下秆撮,如果這個整數(shù)只是一個NSInteger的普通變量四濒,那么它所占用的內(nèi)存與CPU的位數(shù)有關,在32位CPU下占4個字節(jié)职辨,在64位CPU下是占8個字節(jié)的盗蟆,而指針類型的大小通常通常也與CPU的位數(shù)有關,在32位CPU下占4個字節(jié)舒裤,在64位CPU下是占8個字節(jié)喳资。
所以,一個普通的iOS程序腾供,如果沒有Tagged Pointer(標記指針)對象仆邓,從32位機器遷移到64位機器中后,雖然邏輯上沒有任何變化台腥,但這種NSNumber宏赘、NSDate一類的對象所占用的內(nèi)存會翻倍。

再看看效率黎侈,為了存儲和訪問一個NSNumber對象察署,我們需要在堆上為其分配內(nèi)存,另外還要維護它的引用計數(shù)峻汉,管理它的生命周期贴汪。這些都給程序增加了額外的邏輯脐往,造成運行效率上的損失。
image.png

2扳埂、Tagged Pointer介紹

由于NSNumber业簿、NSDate一類的變量本身的值需要占用的內(nèi)存大小常常不需要8個字節(jié),拿整數(shù)來說阳懂,4個字節(jié)所能表示的有符號整數(shù)就可以達到20多億(注:2^31 = 2147483648梅尤,另外一位作為符號位),對于絕大多數(shù)情況都是可以處理的岩调!

所以我們可以將一個對象的指針拆成兩部分巷燥,一部分直接保存數(shù)據(jù),另一部分作為特殊標記号枕,表示這是一個特別的指針缰揪,不指向任何一個地址。所以葱淳,引入了Tagged Pointer對象之后钝腺,64位CPU下NSNumber的內(nèi)存圖變成了下面這樣:
image.png
對此我們可以用代碼進行驗證:
        NSNumber *number1 = @1;
        NSNumber *number2 = @2;
        NSNumber *number3 = @3;
        NSNumber *numberFFFF = @(0xFFFF);
        
        NSLog(@"number1 pointer is %p", number1);
        NSLog(@"number2 pointer is %p", number2);
        NSLog(@"number3 pointer is %p", number3);
        NSLog(@"numberFFFF pointer is %p", numberFFFF);
image.png

我們將NSNumber類型的指針在64位CPU下直接輸出,除去末尾的2和開頭的0xb其他的數(shù)字剛好表示響應NSNumber的值赞厕。猜測:末尾的2和最開頭0xb就是Tagged Pointer的特殊標記Q藓?
我們繼續(xù)驗證坑傅,嘗試方一個8字節(jié)長的整數(shù)到NSNumber實例中僵驰,這樣的實例,Tagged Pointer無法將其按上面的壓縮方式來保存:

        NSNumber *bigNumber = @(0xFFFFFFFFFFFFFFFF);
        NSLog(@"bigNumber pointer is %p", bigNumber);

打印結果:bigNumber pointer is 0x600000029520

Tagged Pointer特點:

  1. Tagged Pointer專門用來存儲小的對象唁毒,例如NSNumber和NSDate蒜茴。
  2. Tagged Pointer指針的值不再是地址,而是真正的值浆西。所以粉私,實際上它不再是一個對象了,它只是一個披著對象"皮"的普通變量而已近零。所以诺核,它的內(nèi)存并不存儲在堆中,也不需要malloc和free久信!
  3. 在內(nèi)存讀取上有著以前3倍的效率窖杀,創(chuàng)建時比以前快106倍!

結論:

  • 當8個字節(jié)可以承載用于表示的數(shù)值時裙士,系統(tǒng)會以Tagged Pointer的方式生成指針入客,如果8個字節(jié)承載不了時,則又用以前的方式生產(chǎn)普通的指針!桌硫!
  • 引入Tagged Pointer夭咬,不但減少了64位機器下程序的內(nèi)存占用,還提高了運行效率铆隘,完美地解決了小內(nèi)存對象在存儲和訪問效率上的問題W慷妗!

3膀钠、注意事項和實現(xiàn)細節(jié)

3.1 isa指針

Tagged Pointer的引入也帶來了問題掏湾,即Tagged Pointer并不是真正的對象,而是一個偽對象托修,所以你如果完全把它當做對象來使用忘巧,可能會出問題,比如:所有對象都有isa指針睦刃,而Tagged Pointer其實是沒有的,因為它不是真正的對象十酣,以為不是真正的對象涩拙,所以你如果直接訪問Tagged Pointer的isa成員的話,在編譯時會有警告:

obj->isa

我們應該盡量避免上述寫法耸采,應該換成相應的方法調(diào)用兴泥,如isKindOfClass和object_getClass。只要避免在代碼中直接訪問對象的isa就可以了虾宇!

3.2 引用計數(shù)

對于64位設備搓彻,蘋果除了引入Tagged Pointer來優(yōu)化小的對象外,對于普通的對象嘱朽,其isa指針也進行了優(yōu)化和調(diào)整旭贬!
在32位環(huán)境下,對象的引用計數(shù)都保存在一個外部的表中搪泳,每一個對象的Retain操作稀轨,實際上包括如下5個步驟:

  1. 獲得全局的記錄引用計數(shù)的hash表
  2. 為了線程安全,給該hash表枷鎖
  3. 查找到目標對象的引用計數(shù)值
  4. 將該引用計數(shù)值加1岸军,寫回hash表
  5. 給該hash表解鎖

從上面步驟來看奋刽,為了保證線程安全,對引用計數(shù)的增減操作都要先鎖定這個表艰赞,這從性能上看是非常差的佣谐!
而在64位環(huán)境下,isa指針也是64位的方妖,實際作為指針部分只用到了33位狭魂,剩余31位蘋果使用了類似Tagged Pointer的概念,其中19位將保存對象的引用計數(shù),這樣對引用計數(shù)的操作只需要修改這個指針即可趁蕊。只有當引用計數(shù)超出19位坞生,才會將引用計數(shù)保存到外部表,而這種情況是很少掷伙,所以這樣引用計數(shù)的更改銷量會更高是己!
在64位環(huán)境下,新的retain操作包括如下5個步驟:

  1. 檢查isa指針上面的標記位任柜,看引用計數(shù)是否保存在isa變量中卒废,如果不是,則使用以前的步驟宙地,否則執(zhí)行第2步
  2. 檢查當前對象是否正在釋放摔认,如果是,則不做任何事情
  3. 增加該對象的引用計數(shù)宅粥,但是并不是馬上寫回到isa變量中
  4. 檢查增加后的引用計數(shù)的值是否能夠被19位表示参袱,如果不是,則切換成以前的辦法秽梅,否則執(zhí)行第5步
  5. 進行一個原子的寫操作抹蚀,將isa的值寫回

由于沒有了全局的加鎖操作,所以引用計數(shù)的更改更快了企垦!

3.3 isa的bit位

image.png
union isa_t 
{
    isa_t() { }
    isa_t(uintptr_t value) : bits(value) { }
    Class cls;
    uintptr_t bits;
#if SUPPORT_NONPOINTER_ISA
# if __arm64__
#   define ISA_MASK        0x00000001fffffff8ULL
#   define ISA_MAGIC_MASK  0x000003fe00000001ULL
#   define ISA_MAGIC_VALUE 0x000001a400000001ULL
    struct {
        uintptr_t indexed           : 1;
        uintptr_t has_assoc         : 1;
        uintptr_t has_cxx_dtor      : 1;
        uintptr_t shiftcls          : 30; // MACH_VM_MAX_ADDRESS 0x1a0000000
        uintptr_t magic             : 9;
        uintptr_t weakly_referenced : 1;
        uintptr_t deallocating      : 1;
        uintptr_t has_sidetable_rc  : 1;
        uintptr_t extra_rc          : 19;
#       define RC_ONE   (1ULL<<45)
#       define RC_HALF  (1ULL<<18)
    };
# elif __x86_64__
#   define ISA_MASK        0x00007ffffffffff8ULL
#   define ISA_MAGIC_MASK  0x0000000000000001ULL
#   define ISA_MAGIC_VALUE 0x0000000000000001ULL
    struct {
        uintptr_t indexed           : 1;
        uintptr_t has_assoc         : 1;
        uintptr_t has_cxx_dtor      : 1;
        uintptr_t shiftcls          : 44; // MACH_VM_MAX_ADDRESS 0x7fffffe00000
        uintptr_t weakly_referenced : 1;
        uintptr_t deallocating      : 1;
        uintptr_t has_sidetable_rc  : 1;
        uintptr_t extra_rc          : 14;
#       define RC_ONE   (1ULL<<50)
#       define RC_HALF  (1ULL<<13)
    };
# else
    // Available bits in isa field are architecture-specific.
#   error unknown architecture
# endif
// SUPPORT_NONPOINTER_ISA
#endif
};

SUPPORT_NONPOINTER_ISA 用于標記是否支持優(yōu)化的 isa 指針环壤,其字面含義意思是 isa 的內(nèi)容不再是類的指針了,而是包含了更多信息钞诡,比如引用計數(shù)郑现,析構狀態(tài),被其他 weak 變量引用情況荧降。判斷方法也是根據(jù)設備類型:

#if !__LP64__  ||  TARGET_OS_WIN32  ||  TARGET_IPHONE_SIMULATOR  ||  __x86_64__
#   define SUPPORT_NONPOINTER_ISA 0
#else
#   define SUPPORT_NONPOINTER_ISA 1
#endif

我們可以看到接箫,模擬器也是不支持Tagged Pointer的!
參考:isa 指針 和 IMP 指針

?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末誊抛,一起剝皮案震驚了整個濱河市列牺,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌拗窃,老刑警劉巖瞎领,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異随夸,居然都是意外死亡九默,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進店門宾毒,熙熙樓的掌柜王于貴愁眉苦臉地迎上來驼修,“玉大人,你說我怎么就攤上這事∫腋鳎” “怎么了墨礁?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長耳峦。 經(jīng)常有香客問我恩静,道長,這世上最難降的妖魔是什么蹲坷? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任驶乾,我火速辦了婚禮,結果婚禮上循签,老公的妹妹穿的比我還像新娘级乐。我一直安慰自己,他們只是感情好县匠,可當我...
    茶點故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布风科。 她就那樣靜靜地躺著,像睡著了一般聚唐。 火紅的嫁衣襯著肌膚如雪丐重。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天杆查,我揣著相機與錄音,去河邊找鬼臀蛛。 笑死亲桦,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的浊仆。 我是一名探鬼主播客峭,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼抡柿!你這毒婦竟也來了舔琅?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤洲劣,失蹤者是張志新(化名)和其女友劉穎备蚓,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體囱稽,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡郊尝,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了战惊。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片流昏。...
    茶點故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出况凉,到底是詐尸還是另有隱情谚鄙,我是刑警寧澤,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布刁绒,位于F島的核電站闷营,受9級特大地震影響,放射性物質發(fā)生泄漏膛锭。R本人自食惡果不足惜粮坞,卻給世界環(huán)境...
    茶點故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望初狰。 院中可真熱鬧莫杈,春花似錦、人聲如沸奢入。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽腥光。三九已至关顷,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間武福,已是汗流浹背议双。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留捉片,地道東北人平痰。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像伍纫,于是被迫代替她去往敵國和親宗雇。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,916評論 2 344

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