1. 程式人生 > >寫給Unity開發者的iOS記憶體除錯指南

寫給Unity開發者的iOS記憶體除錯指南

0x00 前言

工作的過程中,常常會發現有小夥伴對Unity的Profiler提供的記憶體資料與某些原生平臺Profiler工具,例如iOS系統和Xcode,所提供的記憶體資料有差異而感到好奇。而且大家對如何解讀原生平臺工具的資料更加感興趣,同樣例如iOS系統和Xcode。最近正好看了一個來自Unite Copenhagen題為 Developing and optimizing a procedural game | The Elder Scrolls Blades - Unite Copenhagen 的演講,其中就涉及到了一些關於iOS記憶體的話題(雖然並不是很詳細)。正好也結合工作中的一些經驗,寫一篇文章來討論一下一個Unity開發者如何處理和iOS記憶體有關的問題。主要內容包括解析iOS系統的記憶體管理,使用Instrument檢視Unity遊戲的記憶體狀況,使用命令列工具深入挖掘Unity遊戲的記憶體問題以及文末小彩蛋。  

0x01 iOS的記憶體管理 - Unity Profiler統計錯了嗎?

首先,我想強調的一點是,Profiler工具所提供的記憶體資料只是一個(組)數字,而且不同的工具存在有不同統計記憶體的策略。因此,一個重要的問題是我們看到的資料究竟是如何獲取的?   而根據所使用的工具不同,該工具用於查詢資料的策略以及開發人員實際要查詢的內容,最後的結果也有可能是不一樣的。因此,如果要尋找一個數字來彙總某個應用或者遊戲的所有記憶體資訊,那麼可能是把問題想簡單了,或者說忽略了系統的複雜性。例如,不同版本的iOS其對記憶體開銷的統計都是有區別的——在iOS12上執行的metal app的記憶體在 Xcode memory gauge的統計是高於iOS11的,這同樣是由於蘋果改變了對記憶體的統計策略,很多之前沒有被統計的記憶體如今也被計算到了記憶體開銷中。而同樣都是iOS,Xcode memory gauge的統計和Instrument中的統計也有可能不完全一致,而早期Instrument的Allocation則主要用來統計heap記憶體,只能說根據各自工具的統計規則,大家都是正確的。因此,把時間浪費在對比不同工具的資料上還不如以一個工具作為標尺來衡量記憶體開銷或者是判斷記憶體的優化是否有效。  
The accounting for purgeable, nonvolatile memory changed beginning in iOS 12 and tvOS 12. In iOS 11 and tvOS 11, allocations with this memory storage mode—commonly used by Metal apps to store buffers, textures, and state objects—weren’t counted toward an app’s memory limit and weren’t presented in tools like Xcode memory gauge.
  所以,瞭解作業系統是如何管理記憶體就變得十分重要,對於如何解讀Profiler工具提供的資料也很有幫助。接下來我們先來討論一下iOS系統對記憶體的管理機制,之後再來分別看看Xcode抓取的記憶體資料和Unity抓取的記憶體資料。   首先,每一個程序都會有一個地址空間。其範圍由指標size支援,比如32bit或64bit。並且地址空間首先會分為多個區域(regions),然後將這些區域細分為4KB(早期版本)或16KB(A7之後)為單位的page,這些page繼承了該region的各種屬性,例如是否是隻讀、可讀寫等等。當然,有些page可能存放的資料比這個page的尺寸要小,有的資料可能需要好幾page才能存放,但是系統的記憶體單位是16kb的page,所以系統統計的記憶體開銷約等於page的數量 x page的大小。   當然,系統還有真實的實體記憶體。  

Virtual memory vs Resident memory

  ref: WWDC 2013   通過虛擬記憶體使我們能夠建立從該地址空間到真實實體記憶體的對映,這點我想這些大家應該都知道。而對映其實是一個很有趣的事情。因為從每一個app程序的角度來看,它擁有所有的記憶體,即虛擬記憶體,但事實上只有一部分虛擬記憶體被對映到了真實的實體記憶體上,這部分被對映到實體記憶體的部分就是所謂的Resident memory。   就像上面這個圖中描述的一樣,一個app分配了記憶體,可以看到在虛擬記憶體上分配了4個region,其中第3個region包括了13個page。 但此時,真正對映到實體記憶體上的只有6個page。而虛擬記憶體到真實實體記憶體的對映發生在對記憶體的第一次使用時,比如從記憶體中讀取資料或是向記憶體中寫資料。Resident memory同樣也是Virtual memory,只不過這部分Virtual memory已經對映到了真實的實體記憶體。 我想大家可能都通過XCode或者Instrument的統計看到過類似的資料,例如Instrument的VM Tracker中就分別列出了Resident和Virtual Size。  

Dirty memory vs Clean memory

  page有可能是dirty的也有可能是clean的。要如何區分dirty和clean呢?簡單的說,dirty的頁就是我們的app或者遊戲對這個page的內容進行了修改即分配了記憶體同時也修改了記憶體的內容,常見的就是malloc在heap上分配的記憶體。這部分記憶體是不能被回收的,因為這些資料顯然需要被儲存在記憶體中以保證程式正常的執行。   而clean的頁則是沒有對其內容進行修改,可以被系統收回和重新建立的。例如記憶體對映檔案(Memory-mapped file),如果作業系統需要更多的記憶體,那麼就可以將其丟棄。因為系統總是可以從磁碟中重新載入它,建立記憶體空間和磁碟上檔案的對映關係。clean的記憶體是可以被釋放和重新建立的。但是可以看到,雖然Memory-mapped file並沒有消耗真實的實體記憶體,但是它消耗了程序的虛擬記憶體。   除此之外還有可執行檔案的__TEXT段以及一些framework的DATA CONST段,也會歸為clean memory。   在WWDC2018上,iOS的開發人員舉了一個很形象的例子。即分配20,000個integers組成的array,此時會有page被建立,如果只對第一個元素和最後一個元素賦值,則第一個page和最後一個page——即首尾元素所在的page——會變成dirty,但是首尾之間的page仍然是clean,即只分配了記憶體而沒有修改或寫資料。     ref: WWDC 2018  

Compressed memory

  當記憶體吃緊時,會回收clean page。而dirty page是不能被回收的,那麼如果dirty memory過多會如何呢?在iOS7之前,如果程序的dirty memory過高則系統會直接終止程序。iOS7之後,引入了Compressed Memory的機制。由於iOS沒有傳統意義上的disk swap 機制(mac OS有),因此我們在蘋果的Profiler工具中看到的Swapped Size指的其實就是Compressed Memory。   iOS7之後,作業系統可以通過記憶體壓縮器來對dirty記憶體進行壓縮。首先,針對那些有一段時間沒有被訪問的dirty pages(多個page),記憶體壓縮器會對其進行壓縮。但是,在這塊記憶體再次被訪問時,記憶體壓縮器會對它解壓以正確的訪問。舉個例子,某個Dictionary使用了3個page的記憶體,如果一段時間沒有被訪問同時記憶體吃緊,則系統會嘗試對它進行壓縮從3個page壓縮為1個page從而釋放出2個page的記憶體。但是如果之後需要對它進行訪問,則它佔用的page又會變為3個。  

Unity Profiler錯了嗎?

  可以看到,從作業系統記憶體管理的角度來看,一個程序的記憶體其實是十分複雜的。而Unity記錄的記憶體資料,以“Reserved Total - Unity”為例,則主要來自引擎內MemoryManager的記錄。MemoryManager會根據不同的情況呼叫對應的Allocator來進行引擎的記憶體分配。       例如我們可以以Unity 3D Game Kit這個免費專案為例,使用Instrument來檢視一下它的記憶體分配。       可以看到MemoryManager呼叫了UnityDefaultAllocator。 而下圖的這個分配則使用了IphoneNewLabelAllocator來分配記憶體。       也就是說Unity的程式碼分配的記憶體,Unity是會進行記錄的。但是我們可以看到除了Unity的程式碼本身分配的記憶體,還有很多framework或者第三方library也會分配記憶體。但是這部分記憶體,Unity的Profiler是不會記錄的。  

0x02 使用Instrument除錯Unity 遊戲的記憶體

  這部分我推薦Valentin Simonov的這篇文章Understanding iOS Memory (WiP),對使用Instrument除錯記憶體介紹的十分清晰。  

0x03 使用命令列工具深入挖掘記憶體問題

  除了使用Instrument來調查記憶體問題之外,我們還可以通過很棒的Xcode memory debugger工具來查詢記憶體問題。尤其是將Memgraph匯出後,還可以藉助各種命令列工具來輔助調查以獲取更多資訊。       而且有時大家也會抱怨說在Xcode的Memory Report頁面看到的記憶體資料有時候不僅和Unity Profiler不一樣,有時甚至和Instrument等蘋果自己的效能工具數值也不一樣。上文已經說過了,不同的工具有不同的資料是正常的。但是我們同樣可以通過Memgraph和命令列工具來檢視一下,Memory Report的資料側重什麼內容。   還是以Unity 3D Kit這個工程作為演示,測試裝置為iPhone X,不過在開始之前我們首先需要開啟Scheme -> Run -> Diagnostics -> Malloc Stack選項。       運行遊戲後從主選單點選開始遊戲載入第一個場景,我們可以在Memory Report中看到此時的記憶體已經達到了1.48G。但是Memory Report中它的記憶體刻度仍然在綠色部分,所以實事求是的講Memory Report的刻度並不是一個好的優化建議,因為這個記憶體開銷在iphone7上就直接會導致遊戲被系統中止。      

Animation Leak?

  我們直接進入到Xcode memory debugger,如果想要在這裡檢查是否有記憶體leak的問題,可以點選Filter中的選項。這裡有一個常見的假“leak”情況。    

相關推薦

Unity開發者iOS記憶體除錯指南

0x00 前言 工作的過程中,常常會發現有小夥伴對Unity的Profiler提供的記憶體資料與某些原生平臺Profiler工具,例如iOS系統和Xcode,所提供的記憶體資料有差異而感到好奇。而且大家對如何解讀原生平臺工具的資料更加感興趣,同樣例如iOS系統和Xcode。最近正好看了一個來自Unite C

[譯] React 開發者的自定義元素指南

原文地址:A Guide to Custom Elements for React Developers 原文作者:CHARLES PETERS 譯文出自:掘金翻譯計劃 本文永久連結:github.com/xitu/gold-m… 譯者:子非 校對者:Xcco, I

Android 開發者的混淆使用手冊

使用 ria proguard 成員 情況 classname 參與 RM efault 毫無疑問,混淆是打包過程中最重要的流程之一,在沒有特殊原因的情況下,所有 app 都應該開啟混淆。 首先,這裏說的的混淆其實是包括了代碼壓縮、代碼混淆以及資源壓縮等的優化過程。依靠 P

Android開發者的混淆使用手冊

元素 配置 final 文件 orm ide build tco 方法名 轉自:http://huihui.name/2016/10/23/%E5%86%99%E7%BB%99Android%E5%BC%80%E5%8F%91%E8%80%85%E7%9A%84%E6%B7

Unity 接收 IOS 記憶體不足的事件回撥。

1. unity 2017 新版本 https://docs.unity3d.com/ScriptReference/Application-lowMemory.html 直接註冊一下事件 2. 5.5 左右的 需要自己和IOS關聯起來。 方法一.用 XUPorter 工具 修改程式碼

Android 開發者的 Gradle 系列(一)基本姿勢

如果你對本文感興趣,也許你對我的公眾號也會有興趣,可掃下方二維碼或搜尋公眾微訊號:mxszgg 本文基於 Android Gradle plugin 3.0.1 Gradle 介紹 筆者認為能夠戳進這篇文章的讀者十之八九也是知道 Gradle 可以用

前端開發者看的 go 入門教程】mac 下 go開發環境搭建

mac 安裝 go 1. 安裝 go brew install go 2. 配置Go環境變數GOPATH和GOBIN cd ~ ls -all //檢視是否存在.bash_profile檔案 touch .bash_profile // 如果沒有的話,就建立 vi .

前端開發者看的 go 入門教程】window下 go開發環境搭建

win go 開發環境 1. 下載安裝包 我的電腦是 win10 64位,如果你的電腦也是64位的,可以下載 go1.9.2 , 如果是 32位的,go 牆內下載地址, 自己找一下對應的版本就行了。 2. 雙擊 msi 檔案進行安裝 一直點選 next 就行,不過預設是安

一份極客的智慧家居指南

這是一個探索性專案,旨在提供一個完整的智慧家居搭建指南。在此,可點選連結檢視視訊演示,具體系統架構圖如下所示:方案簡介:使用 Home Assistant、HomeBridge 作為智慧家居的核心 使用 Amazon Echo 作為語音輸入工具(當前僅支援英語)

程式猿的把妹指南:概述篇

每個女人都是一套系統 每個女人,都是一套複雜的系統,只不過,這套系統不是由程式設計師創造的,而是由大自然進化而成的。大部分程式猿不太懂得如何泡妞,是因為你不熟悉女人這套系統,也沒人教過你如何學習這套系統。本指南將給你提供指引,讓你開始瞭解女人這套系統,並指引你如何征服

程式猿的把妹指南:概述篇

每個女人都是一套系統 每個女人,都是一套複雜的系統,只不過,這套系統不是由程式設計師創造的,而是由大自然進化而成的。大部分程式猿不太懂得如何泡妞,是因為你不熟悉女人這套系統,也沒人教過你如何學習這套系統。本指南將給你提供指引,讓你開始瞭解女人這套系統,並指引你

Android開發者的Java 8簡單入門教程

原創宣告: 該文章為原創文章,未經博主同意嚴禁轉載。簡介:Java 8是在2014年3月釋出的,Android工程師為什麼要關心Java 8呢?理由是Java 8所做的改變比Java歷史上任何一次改變都要深遠。Java 8對於程式設計師的主要好處在於它提供了更多的程

.NET開發者的Python教程(一):引言

距離上一篇博文已過去8個月了,這段時間發生了很多事情導致沒能持續更新部落格。這段時間除了工作繁忙,業餘時間都投入到AI技術的學習中,後面一段時間將會給大家分享我作為一個.NET開發人員在深度學習領域學習的收穫和成果。 《寫給.NET開發者的Python教程》這個系列是第一個想和大家分享的內容,主要是從C#角度

.NET開發者的Python教程(一):C# vs Python: 語言特性、Conda和Jupyter Notebook環境

承接上篇,本文會從語言特性、開發環境和必備工具來帶領大家進入Python的世界。   語言特性   首先一起看下C#和Python在語言特性層面的對比,他們作為截然不同的兩類面向物件高階語言,在語言層面上有何異同。         注:本系列均

.NET開發者的Python教程(二):基本型別和變數

從本文開始,我們就要正式瞭解Python的語法特性了,這章主要介紹基本型別和變數,開始之前先介紹下Python中的標準輸入輸出。   標準輸入輸出   前文舉過TwoSum問題的例子,但是沒有講到標準輸入輸出的處理,因為那部分leetcode平臺幫我們處理了。但實際上標準輸入輸出是非常重

使用Unity進行遊戲開發的建議

知新樹 寧金峰Unity是一款非常流行的遊戲開發軟件。它的功能令人印象深刻,也能夠適應不同的遊戲開發要求。遊戲開發人員可以使用Unity創建任意類型的遊戲,從世界級的RPG遊戲到最受歡迎的增強現實遊戲Pokemon Go。此外,許多初學者通用Unity來學習遊戲開發或遊戲編程。 Unity的真正影響更加多樣化

讀書筆記_自學者的入門指南 >博客園||知識庫

常常 入門指南 大學 轉換 數據結構 svn 導圖 png 結構 it背景缺失 初學者對於IT世界沒有足夠的認知來搭建起一個世界觀。無法把所學的基礎轉換成地圖的一個塊。容易感到失落(不知道學會了一門新技術的意義和使用方向) 1.學科基礎 《高等數學》、《離散數學》、《電子電

程序員的軟件測試指南:人人都可以開發無Bug代碼

軟件測試 TDD 點擊關註異步圖書,置頂公眾號每天與你分享IT好書 技術幹貨 職場知識參與文末話題討論,每日贈送異步圖書。——異步小編一年前,也是端午節,很巧合,本書的一個譯者為另一個譯者的新書《軟件測試價值提升之路》寫序。一年之後,還是端午節,兩位譯者一起為不一樣風格的軟件測試譯著《程序開發人員測試

Android 開發的小程序布局指南,Flex 布局!

反向 android 容易 一起 些許 意思 移動 之間 img 一、序 Hi,大家好,我是承香墨影! 最近在做小程序,驗證一些方向,開發效率確實很快,就是各種微信的審核有點費勁,但是總歸是有辦法解決的。 想要開發一款小程序,其實和我們正常寫一款 App 類似,你需要有精

大忙人的spring cloud 1.x學習指南

enter pla 聯系 logback 加載 bubuko request 線程 ride 這幾天抽空搞了下spring cloud 1.x(2.0目前應該來說還不成熟),因為之前項目中使用dubbo以及自研的rpc框架,所以總體下來還是比較順利,加上spring