本文是作者原創(chuàng)嘹裂,如有理解錯(cuò)誤妄壶,懇請(qǐng)大家指出,如需引用寄狼,請(qǐng)注明出處丁寄。
Blob內(nèi)存管理分析
在caffe的分層結(jié)構(gòu)中,Blob充當(dāng)了內(nèi)存管理的角色泊愧,屏蔽了上層邏輯代碼對(duì)于數(shù)據(jù)的申請(qǐng)釋放的感知伊磺,同時(shí)也屏蔽了底層設(shè)備對(duì)上層邏輯的影響,本文主要分析Blob的管理機(jī)制和實(shí)際內(nèi)存申請(qǐng)單元SyncedMemory 的機(jī)制删咱。
首先我們看一下Blob和SyncedMemory的關(guān)系屑埋,類(lèi)圖如下:
實(shí)際上整個(gè)Blob的實(shí)現(xiàn)就是在SyncedMemory上封裝了一層,所以首先需要分析一下SyncedMemory的實(shí)現(xiàn)機(jī)制痰滋。
SyncedMemory的實(shí)現(xiàn)機(jī)制
SyncedMemory的目的是為了屏蔽上層代碼對(duì)不同硬件設(shè)備的內(nèi)存分配的感知摘能,同時(shí)隱藏了CPU和GPU之間的同步過(guò)程。同時(shí)即寡,SyncedMemory實(shí)現(xiàn)時(shí)徊哑,采用的是 “l(fā)azy”的模式,就是內(nèi)存的實(shí)際申請(qǐng)時(shí)機(jī)是在第一次使用時(shí)進(jìn)行的聪富。有了大體的了解,下面我們來(lái)詳細(xì)分析一下著蟹。
下面是SyncedMemory 提供的一組接口墩蔓,
名稱(chēng) | 功能 |
---|---|
cpu_data() | 獲取CPU數(shù)據(jù)指針 |
gpu_data() | 獲取GPU數(shù)據(jù)指針 |
實(shí)現(xiàn)的代碼如下:
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
}
可以看出梢莽,每次調(diào)用接口時(shí),都會(huì)有 to_cpu() 和 to_gpu() 的操作,那么這兩個(gè)操作是什么作用呢奸披,我們先看下SyncedMemory中的一些關(guān)鍵參數(shù):
名稱(chēng) | 功能 |
---|---|
cpu_ptr_ | cpu數(shù)據(jù)指針 |
gpu_ptr_ | gpu數(shù)據(jù)指針 |
size_ | 當(dāng)前SyncedMemory需要維護(hù)的數(shù)據(jù)個(gè)數(shù) |
head_ | 當(dāng)前 SyncedMemory處于的狀態(tài) |
前三個(gè)都比較好理解昏名,最后一個(gè)比較特殊,它維護(hù)的是 SyncedMemory 當(dāng)前的狀態(tài)阵面,分為 UNINITIALIZED轻局,HEAD_AT_GPU,HEAD_AT_CPU 样刷,SYNCED 四中狀態(tài)÷仄耍現(xiàn)在介紹一下具體的流程,當(dāng)?shù)谝淮握{(diào)用 to_cpu()時(shí)置鼻, head_處于UNINITIALIZED狀態(tài)镇饮,那么系統(tǒng)會(huì)調(diào)用 CPU的申請(qǐng)內(nèi)存的方式去獲得內(nèi)存區(qū)域,之后設(shè)置 head_ = HEAD_AT_CPU
,如果中間過(guò)程沒(méi)有GPU設(shè)備則不會(huì)有狀態(tài)變動(dòng)箕母,如果中間有代碼調(diào)用了 to_gpu() ,則會(huì)發(fā)現(xiàn) head_處于 HEAD_AT_CPU 狀態(tài)储藐,此時(shí)會(huì)調(diào)用同步函數(shù),將數(shù)據(jù)從CPU同步到GPU嘶是, 之后如果又回到CPU上钙勃,則同樣會(huì)發(fā)現(xiàn) head_ 處于HEAD_AT_GPU的狀態(tài),那么又會(huì)調(diào)用相應(yīng)的同步代碼聂喇,將數(shù)據(jù)同步回CPU辖源,通過(guò) head_這樣一個(gè)狀態(tài)參數(shù)屏蔽了GPU和CPU間的申請(qǐng)和切換的不同。
所以上層業(yè)務(wù)只需要知道當(dāng)前自己需要的是CPU還是GPU的數(shù)據(jù)授帕,然后調(diào)用不同的接口同木,就可以完成數(shù)據(jù)獲取的操作。
Blob的實(shí)現(xiàn)分析
了解了SyncedMemory的實(shí)現(xiàn)跛十,再來(lái)看Blob 就較為簡(jiǎn)單了彤路,它僅僅做了一些上層的管理邏輯,向外界提供了幾個(gè)關(guān)鍵的接口:
名稱(chēng) | 功能 |
---|---|
cpu_data() | 獲取CPU數(shù)據(jù)指針芥映,不能改變數(shù)據(jù)內(nèi)容 |
mutable_cpu_data() | 獲取CPU數(shù)據(jù)指針洲尊,可以改變數(shù)據(jù)內(nèi)容 |
gpu_data() | 獲取GPU數(shù)據(jù)指針,不能改變數(shù)據(jù)內(nèi)容 |
mutable_gpu_data() | 獲取GPU數(shù)據(jù)指針奈偏,可以改變數(shù)據(jù)內(nèi)容 |
Reshape() | 調(diào)整數(shù)據(jù)的維度信息 |
前四個(gè)就是對(duì) SyncedMemory 的 cpu_data() 和 gpu_data()的封裝,只需要確保每次獲取數(shù)據(jù)前都調(diào)用相對(duì)的 to_cpu 或者 to_gpu就可以了坞嘀。對(duì)于最后一個(gè)Reshape函數(shù),主要是為了調(diào)整維度信息惊来,同時(shí)可能是出于適配多種數(shù)據(jù)格式的目的丽涩,所以提供3個(gè)重載函數(shù),如下:
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);
前兩個(gè)重載函數(shù)僅僅進(jìn)行了數(shù)據(jù)格式的轉(zhuǎn)換,然后調(diào)用第三個(gè)函數(shù)矢渊,所以 void Blob::Reshape(const vector<int>& shape);
才是實(shí)際的執(zhí)行者继准,這里需要介紹一下Blob里面較為關(guān)鍵的幾個(gè)參數(shù):
名稱(chēng) | 功能 |
---|---|
data_ | 數(shù)據(jù)的實(shí)際存儲(chǔ)位置 |
shape_data_ | 數(shù)據(jù)的維度信息存儲(chǔ)位置(NCHW) |
capacity_ | 當(dāng)前數(shù)據(jù)塊的大小 |
count_ | reshape后的數(shù)據(jù)塊的大小 |
閱讀代碼不難發(fā)現(xiàn),cout_中所存儲(chǔ)的就是所有維度的的乘積矮男,也就是當(dāng)前要reshape到的數(shù)據(jù)大小移必,整個(gè)的reshape 過(guò)程如下:
結(jié)束
以上就是我對(duì)Blob的一些理解,希望對(duì)大家有幫助毡鉴。