從一個例子來看Tagged Pointer特性

一個錯誤例子

@property (nonatomic, strong) NSString *string;

dispatch_queue_t queue = dispatch_queue_create("memoryBeingFreedCase_4", DISPATCH_QUEUE_CONCURRENT);

? ? for (int i = 0; i < 1000000; i++) {

? ? ? ? dispatch_async(queue, ^{

? ? ? ? ? ? self.string = [NSString stringWithFormat:@"The num is %d", i];

? ? ? ? });

}

當運行后, 就會崩潰在給self.string的一行, 錯誤log如下:

malloc: *** error for object 0x600000639480: Non-aligned pointer being freed (2)

錯誤原因

來看下原因是為何? 其實是因為setter方法中, 對strong修飾的屬性會有一個retain和release的操作。 在并發多線程中的賦值操作中, 都是對_string指針進行的操作, 可能在_string剛剛被release后進行第3行代碼的賦值操作。這時_string指向的內存地址是已經被釋放了, 所以造成上面的錯誤。

- (void)setString:(NSString *)string {

? ? [string retain];? //1

? ? [_stirng release];//2

? ? _string = string; //3

}

解決方案

1.將并發執行的任務改為串行執行。

2.將屬性開啟atomic原子特性。

3.利用Tagged Pointer特性。

前兩個方案的具體方法就不絮述了, 只說第三個方案。如果將上面的例子中, 改動一行代碼, 重新運行。

self.string = [NSString stringWithFormat:@"%d", i];


這時, 你會發現, 竟然沒有問題了, 這究竟為什么呢, 到底什么這么神奇呢? 下面直接引入本文主題Tagged Pointer。

Tagged Pointer

2013年9月, 蘋果發布iPhone5s, 其搭載了蘋果A7處理器, 是首個采用64位架構的處理器。關于iPhone系列的處理器指令集可以參閱之前寫過一篇關于Architectures與指令集架構的博客。也是從采用64位處理器后, 為了節省內存和提高執行效率,蘋果提出了Tagged Pointer的概念。對于64位程序,引入Tagged Pointer后,相關邏輯能減少一半的內存占用,以及3倍的訪問速度提升,100倍的創建、銷毀速度提升。

Tagged Pointer 之前

比如, NSInteger類型的變量,它所占用的內存是與處理器的位數有關,在32位CPU下占4個字節,在64位CPU下是占8個字節的。而指針類型的大小通常也是與CPU位數相關,一個指針所占用的內存在32位CPU下為4個字節,在64位CPU下也是8個字節。

所以在沒有Tagged Pointer對象之前,從32位機器遷移到64位機器中后,雖然邏輯沒有任何變化,但這種NSNumber、NSDate一類的對象所占用的內存會翻倍。而且從效率上來說,我們需要在堆上為其分配內存,另外還要維護它的引用計數,管理它的生命期。這些都給程序增加了額外的邏輯,造成運行效率上的損失。

Tagged Pointer 之后

為了改進上面提到的內存占用和效率問題,蘋果提出了Tagged Pointer對象。由于NSNumber、NSDate一類的變量本身的值需要占用的內存大小常常不需要8個字節。我們可以將一個對象的指針拆成兩部分,一部分直接保存數據,另一部分作為特殊標記,表示這是一個特別的指針,不指向任何一個地址。所以在這總共8字節的內存中, 把標記位除去后, 其他的內存大小都可以存儲數據。所以,引入了Tagged Pointer對象之后,其在64位處理器下的內存圖變成了以下這樣:

而且, 如果你所要存儲的數據大小超出Tagged Pointer對象可存儲大小的話, 系統將不會以Tagged Pointer的方式, 將會以普通對象的方式來保存。所以, 這個優化并不需要人為的干預。

代碼驗證

? ? NSMutableString *string = [NSMutableString stringWithString:@"1"];

? ? for(int i = 0; i < 20; i++){

? ? ? ? NSNumber *number = @([string longLongValue]);

? ? ? ? NSLog(@"%@: %p---%p", [number class], number, &number);

? ? ? ? [string appendString:@"1"];

? ? }

以NSNumber類型舉例, 打印結果:

__NSCFNumber: 0xb000000000000013---0x7ffee2f43698

__NSCFNumber: 0xb0000000000000b3---0x7ffee2f43698

__NSCFNumber: 0xb0000000000006f3---0x7ffee2f43698

__NSCFNumber: 0xb000000000004573---0x7ffee2f43698

__NSCFNumber: 0xb00000000002b673---0x7ffee2f43698

__NSCFNumber: 0xb0000000001b2073---0x7ffee2f43698

__NSCFNumber: 0xb0000000010f4473---0x7ffee2f43698

__NSCFNumber: 0xb00000000a98ac73---0x7ffee2f43698

__NSCFNumber: 0xb000000069f6bc73---0x7ffee2f43698

__NSCFNumber: 0xb000000423a35c73---0x7ffee2f43698

__NSCFNumber: 0xb000002964619c73---0x7ffee2f43698

__NSCFNumber: 0xb000019debd01c73---0x7ffee2f43698

__NSCFNumber: 0xb000102b36211c73---0x7ffee2f43698

__NSCFNumber: 0xb000a1b01d4b1c73---0x7ffee2f43698

__NSCFNumber: 0xb00650e124ef1c73---0x7ffee2f43698

__NSCFNumber: 0xb03f28cb71571c73---0x7ffee2f43698

__NSCFNumber: 0xb27797f26d671c73---0x7ffee2f43698

__NSCFNumber: 0x6000006297c0---0x7ffee2f43698

__NSCFNumber: 0x6000006297c0---0x7ffee2f43698

__NSCFNumber: 0x6000006297c0---0x7ffee2f43698

通過結果分析, 在打印地址中除去最后的數字最末尾的3以及最開頭的0xb, 其它數字剛好表示了相應NSNumber的值。可見,蘋果確實是將值直接存儲到了指針本身里面。也可能數字最末尾的3以及最開頭的0xb就是蘋果對于Tagged Pointer的特殊標記。在最后的三行打印結果中,由于Tagged Pointer無法將其按上面的壓縮方式來保存,那么應該就會以普通對象的方式來保存, 所以打印的結果是棧區和堆區的內存地址。

? ? NSMutableString *string2 = [NSMutableString stringWithString:@"1"];

? ? for( int i = 0; i < 14; i++){

? ? ? ? NSString *strFor = [[string2 mutableCopy] copy];

? ? ? ? NSLog(@"%@: %p---%p", [strFor class], strFor, &strFor);

? ? ? ? [string2 appendString:@"1"];

? ? }

以NSString類型舉例, 打印結果:

NSTaggedPointerString: 0xa000000000000311---0x7ffee9e64698

NSTaggedPointerString: 0xa000000000031312---0x7ffee9e64698

NSTaggedPointerString: 0xa000000003131313---0x7ffee9e64698

NSTaggedPointerString: 0xa000000313131314---0x7ffee9e64698

NSTaggedPointerString: 0xa000031313131315---0x7ffee9e64698

NSTaggedPointerString: 0xa003131313131316---0x7ffee9e64698

NSTaggedPointerString: 0xa313131313131317---0x7ffee9e64698

NSTaggedPointerString: 0xa0079e79e79e79e8---0x7ffee9e64698

NSTaggedPointerString: 0xa1e79e79e79e79e9---0x7ffee9e64698

NSTaggedPointerString: 0xa03def7bdef7bdea---0x7ffee9e64698

NSTaggedPointerString: 0xa7bdef7bdef7bdeb---0x7ffee9e64698

__NSCFString: 0x60400042f560---0x7ffee9e64698

__NSCFString: 0x600000437060---0x7ffee9e64698

__NSCFString: 0x600000436e80---0x7ffee9e64698

這個例子中, 前面的部分類型打印出來都是NSTaggedPointerString, 這就很明顯了。也可能開頭的0xa就是蘋果對于Tagged Pointer的特殊標記。在最后的三行打印結果中,由于Tagged Pointer無法將其按上面的壓縮方式來保存,那么應該就會以普通對象的方式來保存, 所有后面的類型也就變為__NSCFString了。

再看下我在控制臺進行的一些打印:

這個打印結果, 足以說明它是一個特別的指針,且不指向任何一個地址。所有對象都有 isa 指針,而Tagged Pointer其實是沒有的,因為它不是真正的對象。 所以如果你直接訪問Tagged Pointer的isa成員的話,在編譯時將會有警告。

特點

1.Tagged Pointer指針的值不再是地址了,而是真正的值。所以,實際上它不再是一個對象了,它只是一個披著對象皮的普通變量而已。所以,它的內存并不存儲在堆中,也不需要malloc和free。

2.在內存讀取上有著3倍的效率,創建時比以前快106倍。不但減少了64位機器下程序的內存占用,還提高了運行效率。完美地解決了小內存對象在存儲和訪問效率上的問題。

3.這是一個特別的指針,不指向任何一個地址。

4.Tagged Pointer沒有isa指針, 所以其不是真正的對象。

總結

還是得引用唐巧文章中的原話, 蘋果將Tagged Pointer引入,給64位系統帶來了內存的節省和運行效率的提高。Tagged Pointer通過在其最后一個bit位設置一個特殊標記,用于將數據直接保存在指針本身中。因為Tagged Pointer并不是真正的對象,我們在使用時需要注意不要直接訪問其isa變量。

參考文章:

深入理解Tagged Pointer

Let’s Build Tagged Pointers

Tagged Pointer Strings

---------------------

作者:wangyanchang21

來源:CSDN

原文:https://blog.csdn.net/wangyanchang21/article/details/80570863

版權聲明:本文為博主原創文章,轉載請附上博文鏈接!

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容