在2013年9月,蘋果推出了iPhone5s,配備了首個采用64位架構(gòu)的A7雙核處理器筒占,為了節(jié)省內(nèi)存和提高執(zhí)行效率,蘋果提出了標(biāo)記指針(Tagged Pointer)
的概念蜘犁。對于64位程序翰苫,引入Tagged Pointer后,相關(guān)邏輯能減少一半的內(nèi)存占用沽瘦,以及3倍的訪問速度提升革骨,100倍的創(chuàng)建、銷毀速度提升析恋。
原有的對象為什么會浪費(fèi)內(nèi)存良哲?
假設(shè)我們要存儲一個NSNumber對象,其值是一個整數(shù)助隧。正常情況下筑凫,如果這個整數(shù)只是一個NSInteger的普通變量,那么它所占用的內(nèi)存是與CPU的位數(shù)有關(guān)并村,在32位CPU下占4個字節(jié)巍实,在64位CPU下是占8個字節(jié)的。而指針類型的大小通常也是與CPU位數(shù)相關(guān)哩牍,一個指針?biāo)加玫膬?nèi)存在32位CPU下為4個字節(jié)棚潦,在64位CPU下也是8個字節(jié)。
所以一個普通的iOS程序膝昆,如果沒有Tagged Pointer對象丸边,從32位機(jī)器遷移到64位機(jī)器中后,雖然邏輯沒有任何變化荚孵,但這種NSNumber妹窖、NSDate一類的對象所占用的內(nèi)存會翻倍。
效率上的問題收叶,為了存儲和訪問一個NSNumber對象骄呼,需要在堆上為其分配內(nèi)存,另外還要維護(hù)它的引用計數(shù)判没,管理它的生命期蜓萄。這些都給程序增加了額外的邏輯,造成運(yùn)行效率上的損失哆致。
Tagged Pointer
為了改進(jìn)上面提到的內(nèi)存占用和效率問題绕德,蘋果提出了Tagged Pointer對象。由于NSNumber摊阀、NSDate一類的變量本身的值需要占用的內(nèi)存大小常常不需要8個字節(jié)耻蛇,拿整數(shù)來說,4個字節(jié)所能表示的有符號整數(shù)就可以達(dá)到20多億(注:2^31=2147483648胞此,另外1位作為符號位)臣咖,對于絕大多數(shù)情況都是可以處理的。
-
NSNumber
的優(yōu)化
Tagged Pointer一個比較典型的應(yīng)用就是NSNumber漱牵,在64位環(huán)境下夺蛇,對于一般的數(shù)字,NSNumber不用再分配內(nèi)存了酣胀。我們看看NSNumber是如何運(yùn)用Tagged Pointer的:
NSNumber *number1=@1;
NSNumber *number2=@2;
NSNumber *number3=@3;
NSLog(@"number1 pointer is %p", number1);
NSLog(@"number2 pointer is %p", number2);
NSLog(@"number3 pointer is %p", number3);
在64位模擬器中運(yùn)行后刁赦,我得到了如下結(jié)果:
number1 pointer is 0xb000000000000012
number2 pointer is 0xb000000000000022
number3 pointer is 0xb000000000000032
可以看出number1娶聘、number2和number3的值前4位都是0xb,后4位都是0x2(指針的Tag)甚脉,中間就是實(shí)際的取值丸升,因此,這些NSNumber已經(jīng)不需要再分配內(nèi)存(指堆中內(nèi)存)了牺氨,直接可以把實(shí)際的值保存到指針中狡耻,而無需再去訪問堆中的數(shù)據(jù)。這無疑提高的內(nèi)存訪問速度和整體運(yùn)算速度猴凹。也就是說Tagged Pointer本身就可以表示一個NSNumber了夷狰。
那么如果一個數(shù)超過了Tagged Pointer所能表示的范圍,系統(tǒng)會怎么處理郊霎?看看這段代碼:
NSNumber*numberBig=@(0x2233567890ABCDEF);
NSLog(@"numberBig pointer is %p", numberBig);
在64位模擬器中運(yùn)行后沼头,我得到了如下結(jié)果:
代碼 objc:
numberBig pointer is 0x1394026a0
numberBig指針最后4位都是0,應(yīng)該是分配在堆中的對象书劝。因此瘫证,如果NSNumber超出了Tagged Pointer所能表示的范圍,系統(tǒng)會自動采用分配成對象庄撮,可以根據(jù)指針的最后4位是否為0來區(qū)分背捌。
isa指針優(yōu)化
查看NSObject類的頭文件,你會發(fā)現(xiàn)這段定義:
@interface NSObject: <NSObject>
{
Class isa;
}
所有類都繼承自NSObject洞斯,因此每個對象都有一個isa指針指向它所屬的類毡庆。在《ARM64 and You》文章中指出:在32位環(huán)境下,對象的引用計數(shù)都保存在一個外部的表中烙如,而對引用計數(shù)的增減操作都要先鎖定這個表么抗,操作完成后才解鎖。這個效率是非常慢的亚铁。而在64位環(huán)境下蝇刀,isa也是64位,實(shí)際作為指針部分只用到的其中33位徘溢,剩余的部分會運(yùn)用到Tagged Pointer的概念吞琐,其中19位將保存對象的引用計數(shù),這樣對引用計數(shù)的操作只需要原子的修改這個指針即可然爆,如果引用計數(shù)超出19位站粟,才會將引用計數(shù)保存到外部表,而這種情況往往是很少的曾雕,因此效率將會大大提高奴烙。
在WWDC2013的《Session 404 Advanced in Objective-C》視頻中,看到蘋果對于Tagged Pointer特點(diǎn)的介紹:
1.Tagged Pointer專門用來存儲小的對象,例如NSNumber和NSDate
2.Tagged Pointer指針的值不再是地址了切诀,而是真正的值揩环。
實(shí)際上它不再是一個對象了,它只是一個披著對象皮的普通變量而已幅虑。
它的內(nèi)存并不存儲在堆中检盼,也不需要malloc和free。
3.在內(nèi)存讀取上有著3倍的效率翘单,創(chuàng)建時比以前快106倍。
蘋果引入Tagged Pointer蹦渣,不但減少了64位機(jī)器下程序的內(nèi)存占用哄芜,還提高了運(yùn)行效率。完美地解決了小內(nèi)存對象在存儲和訪問效率上的問題柬唯。
Tagged Pointer的引入也帶來了問題认臊,即Tagged Pointer
因為并不是真正的對象,而是一個偽對象锄奢,所以你如果完全把它當(dāng)成對象來使失晴,可能會讓它露馬腳。所有對象都有 isa指針拘央,而Tagged Pointer其實(shí)是沒有的涂屁,因為它不是真正的對象。 因為不是真正的對象灰伟,所以如果你直接訪問Tagged Pointer的isa成員的話拆又,在編譯時將會有警告。
總之:
蘋果將Tagged Pointer引入栏账,給64位系統(tǒng)帶來了內(nèi)存的節(jié)省和運(yùn)行效率的提高帖族。Tagged Pointer通過在其最后一個bit位設(shè)置一個特殊標(biāo)記,用于將數(shù)據(jù)直接保存在指針本身中挡爵。Tagged Pointer并不是真正的對象竖般,使用時需要注意不要直接訪問其isa變量。