之前在簡書的文章,搬遷過來 ^-^
本文是作者原創,如有理解錯誤,懇請大家指出,如需引用,請註明出處。
#Blob記憶體管理分析
在caffe的分層結構中,Blob充當了記憶體管理的角色,遮蔽了上層邏輯程式碼對於資料的申請釋放的感知,同時也遮蔽了底層裝置對上層邏輯的影響,本文主要分析Blob的管理機制和實際記憶體申請單元SyncedMemory 的機制。 首先我們看一下Blob和SyncedMemory的關係,類圖如下:
實際上整個Blob的實現就是在SyncedMemory上封裝了一層,所以首先需要分析一下SyncedMemory的實現機制。
##SyncedMemory的實現機制 SyncedMemory的目的是為了遮蔽上層程式碼對不同硬體裝置的記憶體分配的感知,同時隱藏了CPU和GPU之間的同步過程。同時,SyncedMemory實現時,採用的是 “lazy”的模式,就是記憶體的實際申請時機是在第一次使用時進行的。有了大體的瞭解,下面我們來詳細分析一下。 下面是SyncedMemory 提供的一組介面,
名稱 | 功能 |
---|---|
cpu_data() | 獲取CPU資料指標 |
gpu_data() | 獲取GPU資料指標 |
實現的程式碼如下:
const void* SyncedMemory::cpu_data() {
to_cpu();
return (const void*)cpu_ptr_;
}
const void* SyncedMemory::gpu_data() {
#ifdef USE_CUDA
to_gpu();
return (const void*)gpu_ptr_;
#else
NO_GPU;
return NULL;
#endif // USE_CUDA
}
複製程式碼
可以看出,每次呼叫介面時,都會有 to_cpu() 和 to_gpu() 的操作,那麼這兩個操作是什麼作用呢,我們先看下SyncedMemory中的一些關鍵引數:
名稱 | 功能 |
---|---|
cpu_ptr_ | cpu資料指標 |
gpu_ptr_ | gpu資料指標 |
size_ | 當前SyncedMemory需要維護的資料個數 |
head_ | 當前 SyncedMemory處於的狀態 |
前三個都比較好理解,最後一個比較特殊,它維護的是 SyncedMemory 當前的狀態,分為 UNINITIALIZED,HEAD_AT_GPU,HEAD_AT_CPU ,SYNCED 四中狀態。現在介紹一下具體的流程,當第一次呼叫 to_cpu()時, head_處於UNINITIALIZED狀態,那麼系統會呼叫 CPU的申請記憶體的方式去獲得記憶體區域,之後設定 head_ = HEAD_AT_CPU
,如果中間過程沒有GPU裝置則不會有狀態變動,如果中間有程式碼呼叫了 to_gpu() ,則會發現 head_處於 HEAD_AT_CPU 狀態,此時會呼叫同步函式,將資料從CPU同步到GPU, 之後如果又回到CPU上,則同樣會發現 head_ 處於HEAD_AT_GPU的狀態,那麼又會呼叫相應的同步程式碼,將資料同步回CPU,通過 head_這樣一個狀態引數遮蔽了GPU和CPU間的申請和切換的不同。
所以上層業務只需要知道當前自己需要的是CPU還是GPU的資料,然後呼叫不同的介面,就可以完成資料獲取的操作。
##Blob的實現分析 瞭解了SyncedMemory的實現,再來看Blob 就較為簡單了,它僅僅做了一些上層的管理邏輯,向外界提供了幾個關鍵的介面:
名稱 | 功能 |
---|---|
cpu_data() | 獲取CPU資料指標,不能改變資料內容 |
mutable_cpu_data() | 獲取CPU資料指標,可以改變資料內容 |
gpu_data() | 獲取GPU資料指標,不能改變資料內容 |
mutable_gpu_data() | 獲取GPU資料指標,可以改變資料內容 |
Reshape() | 調整資料的維度資訊 |
前四個就是對 SyncedMemory 的 cpu_data() 和 gpu_data()的封裝,只需要確保每次獲取資料前都呼叫相對的 to_cpu 或者 to_gpu就可以了。對於最後一個Reshape函式,主要是為了調整維度資訊,同時可能是出於適配多種資料格式的目的,所以提供3個過載函式,如下:
void Blob::Reshape(const int num, const int channels,const int height, const int width);
void Blob::Reshape(const BlobShape& shape) ;
void Blob::Reshape(const vector<int>& shape);
複製程式碼
前兩個過載函式僅僅進行了資料格式的轉換,然後呼叫第三個函式,所以 void Blob::Reshape(const vector<int>& shape);
才是實際的執行者,這裡需要介紹一下Blob裡面較為關鍵的幾個引數:
名稱 | 功能 |
---|---|
data_ | 資料的實際儲存位置 |
shape_data_ | 資料的維度資訊儲存位置(NCHW) |
capacity_ | 當前資料塊的大小 |
count_ | reshape後的資料塊的大小 |
閱讀程式碼不難發現,cout_中所儲存的就是所有維度的的乘積,也就是當前要reshape到的資料大小,整個的reshape 過程如下:
##結束 以上就是我對Blob的一些理解,希望對大家有幫助。