深度學習 Caffe 記憶體管理機制理解

賈瀾鵬發表於2019-05-06

之前在簡書的文章,搬遷過來 ^-^
本文是作者原創,如有理解錯誤,懇請大家指出,如需引用,請註明出處。

#Blob記憶體管理分析

在caffe的分層結構中,Blob充當了記憶體管理的角色,遮蔽了上層邏輯程式碼對於資料的申請釋放的感知,同時也遮蔽了底層裝置對上層邏輯的影響,本文主要分析Blob的管理機制和實際記憶體申請單元SyncedMemory 的機制。 首先我們看一下Blob和SyncedMemory的關係,類圖如下:

blob_class.jpg

實際上整個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 過程如下:

reshape.jpg

##結束 以上就是我對Blob的一些理解,希望對大家有幫助。

相關文章