iOS 使用Instruments優化記憶體效能

aron1992發表於2019-04-04

iOS 使用Instruments優化記憶體效能

問題

專案中使用到圖片合成視訊,發現記憶體增長十分的迅速,導致一些因為記憶體引起的問題,本文使用這個案例,結合Instruments工具檢測和分析問題,最終解決記憶體問題。

本文的Demo程式碼 ScreenRecorderTest2

Instruments檢測

檢視某個範圍內的記憶體增長

  1. 開啟Instruments選擇Allocations工具,點選錄製按鈕進行錄製
  2. 使用滑鼠框選出記憶體增長的區域,圖中兩條黑線中的高亮區域

檢視某個範圍內的記憶體增長
檢視某個範圍內的記憶體增長

  • 上面圖表區域是記憶體的視覺化檢視
  • 下面的皮膚區域顯示的是具體的統計資料
    可以看到整體的記憶體增長為16.81M (All Heap Allocations 堆記憶體的分配)

堆記憶體分配的詳細統計資料
點選All Heap Allocations旁邊的小箭頭按鈕可以看堆記憶體分配的詳細統計資料: 

堆記憶體分配的詳細統計資料
堆記憶體分配的詳細統計資料

  • Address:資料的記憶體地址
  • Timestamp:資料建立的時間
  • Size:資料的大小
  • Responsible Library:資料建立的相關的庫
  • Responsible Caller:資料建立的相關的庫的函式呼叫

統計資料表中可以看出AppleJPEG 庫呼叫 applejpeg_decode_create 方法建立了很多的記憶體的資料

右邊區域顯示的是函式的呼叫棧資訊皮膚(Stack Trace),右上角的工字型按鈕可以切換顯示系統函式呼叫

切換顯示系統函式呼叫
切換顯示系統函式呼叫

記憶體分配大小與對應的程式碼呼叫資訊的視覺化顯示
點選呼叫棧中的高亮程式碼可以檢視程式碼詳情和記憶體資訊:

記憶體分配大小與對應的程式碼呼叫資訊的視覺化顯示
記憶體分配大小與對應的程式碼呼叫資訊的視覺化顯示

右邊的標註區域顯示的是記憶體佔用的比例。

分析

上圖中看到buffer物件和image佔用的記憶體最大,但是buffer在每次使用之後都會呼叫CVPixelBufferRelease釋放對應的記憶體,不會有記憶體的問題,image物件釋放的不及時,會在整個while迴圈塊中保留一段時間,導致記憶體的增長。
此外image物件釋放不及時和在在同一時間呼叫CVPixelBufferRelease(buffer);釋放buffer也有關係,如果沒有建立buffer和釋放buffer的操作,image物件的增長也不會很明顯,釋放的速度也挺快,下面兩個對照組可以進行對比分析

對照組1:只有在迴圈中建立image物件記憶體增長:
記憶體增長為2.73M

對照組1:只有在迴圈中建立image物件記憶體增長:
對照組1:只有在迴圈中建立image物件記憶體增長:

對照組2:在迴圈中新增autoreleasepool建立image物件記憶體增長:
記憶體增長為492K

對照組2:在迴圈中新增autoreleasepool建立image物件記憶體增長
對照組2:在迴圈中新增autoreleasepool建立image物件記憶體增長

由上可知,使用autoreleasepool可以有效的解決在某個迴圈中建立大量的記憶體敏感型物件導致的記憶體上漲的問題

最終解決方案
在while迴圈內部使用autoreleasepool塊,每次迴圈arc物件得以及時的釋放,記憶體增長從原來的16.81M下降到了只有475K

最終解決方案
最終解決方案

程式碼:

while(i < imageNames.count) {
    // 新增自動釋放池,讓記憶體敏感型的物件(UIImage)及時釋放
    @autoreleasepool {
       // 程式碼省略...
        NSString *imageName = [imageNames objectAtIndex:i];
        NSString* imagePath = [imageSavedDir stringByAppendingPathComponent:imageName];
        UIImage* image = [UIImage imageWithContentsOfFile:imagePath];
        if(adaptor.assetWriterInput.readyForMoreMediaData) {
            i++;
            CMTime frameTime = CMTimeMake(1, fps);
            CMTime lastTime = CMTimeMake(i, fps);
            CMTime presentTime = CMTimeAdd(lastTime, frameTime);
            
            buffer = [self pixelBufferFromCGImage:[image CGImage] size:videoFrameSize];
            // 寫入視訊
            BOOL result = [adaptor appendPixelBuffer:buffer withPresentationTime:presentTime];
            if(buffer) {
                CVPixelBufferRelease(buffer);
            }
            // 程式碼省略...
            [NSThread sleepForTimeInterval:0.05];
        } else {
            NSLog(@"Error: Adaptor is not ready");
            [NSThread sleepForTimeInterval:0.05];
            i--;
        }
    }
}
複製程式碼

總結

以上是使用Instruments解決記憶體問題的總結,如有不妥之處還請不吝賜教
本文的Demo程式碼 ScreenRecorderTest2

相關文章