Caffe源碼解讀:syncedmem類(lèi)

內(nèi)存同步(syncedmem)類(lèi)的作用在于管理主機(jī)(CPU)和設(shè)備(GPU)之間的內(nèi)存分配和數(shù)據(jù)同步捞附,封裝了二者之間的交互操作。

這個(gè)類(lèi)沒(méi)有對(duì)應(yīng)的ProtoBuffer描述枉侧,所以直接看./include/caffe/syncedmem.cpp文件:

#ifndef CAFFE_SYNCEDMEM_HPP_
#define CAFFE_SYNCEDMEM_HPP_

#include <cstdlib>

#include "caffe/common.hpp"

namespace caffe {

// 以頁(yè)鎖定方式分配內(nèi)存,在單GPU時(shí)提示不明顯,多GPU提升很多
// malloc分配標(biāo)準(zhǔn)可分頁(yè)的主機(jī)內(nèi)存担巩,操作系統(tǒng)可能會(huì)將這種內(nèi)存分頁(yè)或者交換到磁盤(pán)上,需要的時(shí)候調(diào)回內(nèi)存没炒,這樣可能會(huì)增加運(yùn)行時(shí)間
// cudaMallocHost分配頁(yè)鎖定的主機(jī)內(nèi)存涛癌,操作系統(tǒng)不會(huì)對(duì)這塊內(nèi)存分頁(yè)或者交換到磁盤(pán)上,可以節(jié)省時(shí)間
inline void CaffeMallocHost(void** ptr, size_t size, bool* use_cuda) {
#ifndef CPU_ONLY
  if (Caffe::mode() == Caffe::GPU) {
    CUDA_CHECK(cudaMallocHost(ptr, size)); // 分配顯存并校驗(yàn)是否分配成功
    *use_cuda = true;
    return;
  }
#endif
  *ptr = malloc(size); // CPU模式下分配內(nèi)存
  *use_cuda = false;
  CHECK(*ptr) << "host allocation of size " << size << " failed";
}

// 和上面函數(shù)相對(duì)應(yīng)的內(nèi)存釋放
inline void CaffeFreeHost(void* ptr, bool use_cuda) {
#ifndef CPU_ONLY
  if (use_cuda) {
    //如果分配內(nèi)存用的是cudaMallocHost分配,即use_cuda為真拳话,cpu中的數(shù)據(jù)也可以用cudaMallocHost分配內(nèi)存  
    CUDA_CHECK(cudaFreeHost(ptr));  
    return;
  }
#endif
  free(ptr);  // 用的malloc分配的內(nèi)存
}

// 負(fù)責(zé)內(nèi)存分配和設(shè)備同步
class SyncedMemory {
 public:
  SyncedMemory()     // 默認(rèn)構(gòu)造函數(shù) 
      : cpu_ptr_(NULL), gpu_ptr_(NULL), size_(0), head_(UNINITIALIZED),
        own_cpu_data_(false), cpu_malloc_use_cuda_(false), own_gpu_data_(false),
        gpu_device_(-1) {}
  explicit SyncedMemory(size_t size)  // 顯式構(gòu)造函數(shù)
      : cpu_ptr_(NULL), gpu_ptr_(NULL), size_(size), head_(UNINITIALIZED),
        own_cpu_data_(false), cpu_malloc_use_cuda_(false), own_gpu_data_(false),
        gpu_device_(-1) {}
  ~SyncedMemory();

  // 對(duì)CPU先匪,GPU數(shù)據(jù)的讀寫(xiě),不贅述弃衍。為什么沒(méi)有set_cpu_diff() ???
  const void* cpu_data();
  void set_cpu_data(void* data);
  const void* gpu_data();
  void set_gpu_data(void* data);
  void* mutable_cpu_data();
  void* mutable_gpu_data();

  // 共享內(nèi)存的4種狀態(tài):未初始化呀非,CPU數(shù)據(jù)有效,GPU數(shù)據(jù)有效镜盯,已同步
  enum SyncedHead { UNINITIALIZED, HEAD_AT_CPU, HEAD_AT_GPU, SYNCED };
  // 返回當(dāng)前共享內(nèi)存的狀態(tài)
  SyncedHead head() { return head_; }
  // 返回存儲(chǔ)空間的尺寸 = 元素?cái)?shù) * 單個(gè)元素所占字節(jié)數(shù)
  size_t size() { return size_; }

#ifndef CPU_ONLY
  void async_gpu_push(const cudaStream_t& stream);
#endif

 private:
  void to_cpu();  // 數(shù)據(jù)同步至cpu
  void to_gpu();  // 數(shù)據(jù)同步至gpu
  void* cpu_ptr_;  // cpu中數(shù)據(jù)的指針
  void* gpu_ptr_;   // gpu中數(shù)據(jù)的指針
  size_t size_;   // 存儲(chǔ)空間的大小
  SyncedHead head_;  // 共享內(nèi)存的狀態(tài)
  bool own_cpu_data_;  // cpu擁有數(shù)據(jù)所有權(quán)
  bool cpu_malloc_use_cuda_;   // 分配cpu內(nèi)存是否用cudaMallocHost()分配岸裙。
  bool own_gpu_data_;  // gpu擁有數(shù)據(jù)所有權(quán)
  int gpu_device_;  // gpu設(shè)備號(hào)

  // 禁用拷貝構(gòu)造以及賦值運(yùn)算符
  // 使用grep可以查到,該宏定義在common.hpp第35行
  // 該宏就是把拷貝構(gòu)造和賦值運(yùn)算符設(shè)置為private而已
  DISABLE_COPY_AND_ASSIGN(SyncedMemory); 
};  // class SyncedMemory

}  // namespace caffe
#endif  // CAFFE_SYNCEDMEM_HPP_

這個(gè)類(lèi)比Blob簡(jiǎn)單多了速缆,下面看對(duì)應(yīng)的./src/caffe/syncedmem.cpp文件:

#include "caffe/common.hpp"
#include "caffe/syncedmem.hpp"
#include "caffe/util/math_functions.hpp"

namespace caffe {

SyncedMemory::~SyncedMemory() {
 // 如果cpu擁有數(shù)據(jù)所有權(quán)
 if (cpu_ptr_ && own_cpu_data_) {
   CaffeFreeHost(cpu_ptr_, cpu_malloc_use_cuda_);
 }
 // 如果數(shù)據(jù)在gpu上
#ifndef CPU_ONLY
 if (gpu_ptr_ && own_gpu_data_) {
   int initial_device;
   cudaGetDevice(&initial_device);  // 獲取使用的gpu設(shè)備號(hào)
   if (gpu_device_ != -1) {
     CUDA_CHECK(cudaSetDevice(gpu_device_));
   }
   CUDA_CHECK(cudaFree(gpu_ptr_));
   cudaSetDevice(initial_device);
 }
#endif  // CPU_ONLY
}

// 數(shù)據(jù)同步到cpu上
inline void SyncedMemory::to_cpu() {
 switch (head_) {
 case UNINITIALIZED:  // 如果是未初始化狀態(tài)降允,只需分配下內(nèi)存就行了
   CaffeMallocHost(&cpu_ptr_, size_, &cpu_malloc_use_cuda_);
   caffe_memset(size_, 0, cpu_ptr_);  // 定義在math_functions.hpp第42行,調(diào)用了memset
   head_ = HEAD_AT_CPU;  // 內(nèi)存狀態(tài)改為cpu擁有所有權(quán)
   own_cpu_data_ = true;
   break;
 case HEAD_AT_GPU:  // GPU擁有數(shù)據(jù)所有權(quán)
#ifndef CPU_ONLY
   if (cpu_ptr_ == NULL) {
     CaffeMallocHost(&cpu_ptr_, size_, &cpu_malloc_use_cuda_);// 要內(nèi)存
     own_cpu_data_ = true;
   }
   caffe_gpu_memcpy(size_, gpu_ptr_, cpu_ptr_); // 數(shù)據(jù)復(fù)制
   head_ = SYNCED;
#else
   NO_GPU;
#endif
   break;
 // 數(shù)據(jù)已經(jīng)為cpu擁有所有權(quán)或者在內(nèi)存共享狀態(tài)艺糜,則什么都不管
 case HEAD_AT_CPU:
 case SYNCED:
   break;
 }
}

// 原理同上
inline void SyncedMemory::to_gpu() {
#ifndef CPU_ONLY
 switch (head_) {
 case UNINITIALIZED:
   CUDA_CHECK(cudaGetDevice(&gpu_device_));
   CUDA_CHECK(cudaMalloc(&gpu_ptr_, size_));
   caffe_gpu_memset(size_, 0, gpu_ptr_);
   head_ = HEAD_AT_GPU;
   own_gpu_data_ = true;
   break;
 case HEAD_AT_CPU:
   if (gpu_ptr_ == NULL) {
     CUDA_CHECK(cudaGetDevice(&gpu_device_));
     CUDA_CHECK(cudaMalloc(&gpu_ptr_, size_)); // 要一塊內(nèi)存
     own_gpu_data_ = true;
   }
   caffe_gpu_memcpy(size_, cpu_ptr_, gpu_ptr_); //數(shù)據(jù)拷貝剧董,調(diào)用了cudaMemcpy函數(shù) 
   head_ = SYNCED;
   break;
 case HEAD_AT_GPU:
 case SYNCED:
   break;
 }
#else
 NO_GPU;
#endif
}

// 獲取cpu中的數(shù)據(jù),只讀
const void* SyncedMemory::cpu_data() {
 to_cpu();
 return (const void*)cpu_ptr_;
}
// 設(shè)置獲取cpu中的數(shù)據(jù)
void SyncedMemory::set_cpu_data(void* data) {
 CHECK(data);
 if (own_cpu_data_) {
   // 調(diào)用這個(gè)函數(shù)的時(shí)候破停,如果cpu內(nèi)有數(shù)據(jù)會(huì)被直接清空翅楼,要注意
   CaffeFreeHost(cpu_ptr_, cpu_malloc_use_cuda_); 
 }
 cpu_ptr_ = data;
 head_ = HEAD_AT_CPU;
 own_cpu_data_ = false;
}
// 獲取gpu中的數(shù)據(jù),只讀
const void* SyncedMemory::gpu_data() {
#ifndef CPU_ONLY
 to_gpu();
 return (const void*)gpu_ptr_;
#else
 NO_GPU;
 return NULL;
#endif
}
// 設(shè)置gpu中的數(shù)據(jù)
void SyncedMemory::set_gpu_data(void* data) {
#ifndef CPU_ONLY
 CHECK(data);
 if (own_gpu_data_) {
   int initial_device;
   cudaGetDevice(&initial_device);
   if (gpu_device_ != -1) {
     CUDA_CHECK(cudaSetDevice(gpu_device_));
   }
 // 調(diào)用這個(gè)函數(shù)的時(shí)候辱挥,如果gpu內(nèi)有數(shù)據(jù)會(huì)被直接清空犁嗅,要注意
   CUDA_CHECK(cudaFree(gpu_ptr_));
   cudaSetDevice(initial_device);
 }
 gpu_ptr_ = data;
 head_ = HEAD_AT_GPU;
 own_gpu_data_ = false;
#else
 NO_GPU;
#endif
}
// 讀寫(xiě)獲取cpu數(shù)據(jù)
void* SyncedMemory::mutable_cpu_data() {
 to_cpu();
 head_ = HEAD_AT_CPU;
 return cpu_ptr_;
}
// 讀寫(xiě)獲取gpu數(shù)據(jù)
void* SyncedMemory::mutable_gpu_data() {
#ifndef CPU_ONLY
 to_gpu();
 head_ = HEAD_AT_GPU;
 return gpu_ptr_;
#else
 NO_GPU;
 return NULL;
#endif
}

// cuda中的流同步,這里傳入一個(gè)異步流晤碘,在計(jì)算的時(shí)候向GPU復(fù)制數(shù)據(jù)褂微。
#ifndef CPU_ONLY
void SyncedMemory::async_gpu_push(const cudaStream_t& stream) {
 CHECK(head_ == HEAD_AT_CPU);
 if (gpu_ptr_ == NULL) {
   CUDA_CHECK(cudaGetDevice(&gpu_device_));
   CUDA_CHECK(cudaMalloc(&gpu_ptr_, size_));
   own_gpu_data_ = true;
 }
 const cudaMemcpyKind put = cudaMemcpyHostToDevice;
 CUDA_CHECK(cudaMemcpyAsync(gpu_ptr_, cpu_ptr_, size_, put, stream));
 // Assume caller will synchronize on the stream before use
 head_ = SYNCED;
}
#endif

}  // namespace caffe

syncedmem類(lèi)比較簡(jiǎn)單,主要是完成cpu和gpu之間的數(shù)據(jù)交互問(wèn)題~

參考資料

  1. 《21天實(shí)戰(zhàn)caffe》
  2. (介紹了流同步)http://blog.csdn.net/sinat_22336563/article/details/68496919
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末园爷,一起剝皮案震驚了整個(gè)濱河市宠蚂,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌童社,老刑警劉巖求厕,帶你破解...
    沈念sama閱讀 216,651評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異扰楼,居然都是意外死亡呀癣,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,468評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén)弦赖,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)项栏,“玉大人,你說(shuō)我怎么就攤上這事蹬竖≌由颍” “怎么了流酬?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,931評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀(guān)的道長(zhǎng)列另。 經(jīng)常有香客問(wèn)我芽腾,道長(zhǎng),這世上最難降的妖魔是什么页衙? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,218評(píng)論 1 292
  • 正文 為了忘掉前任摊滔,我火速辦了婚禮,結(jié)果婚禮上拷姿,老公的妹妹穿的比我還像新娘惭载。我一直安慰自己旱函,他們只是感情好响巢,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,234評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著棒妨,像睡著了一般踪古。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上券腔,一...
    開(kāi)封第一講書(shū)人閱讀 51,198評(píng)論 1 299
  • 那天伏穆,我揣著相機(jī)與錄音,去河邊找鬼纷纫。 笑死枕扫,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的辱魁。 我是一名探鬼主播烟瞧,決...
    沈念sama閱讀 40,084評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼染簇!你這毒婦竟也來(lái)了参滴?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 38,926評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤锻弓,失蹤者是張志新(化名)和其女友劉穎砾赔,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體青灼,經(jīng)...
    沈念sama閱讀 45,341評(píng)論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡暴心,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,563評(píng)論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了杂拨。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片专普。...
    茶點(diǎn)故事閱讀 39,731評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖扳躬,靈堂內(nèi)的尸體忽然破棺而出脆诉,到底是詐尸還是另有隱情甚亭,我是刑警寧澤,帶...
    沈念sama閱讀 35,430評(píng)論 5 343
  • 正文 年R本政府宣布击胜,位于F島的核電站亏狰,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏偶摔。R本人自食惡果不足惜暇唾,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,036評(píng)論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望辰斋。 院中可真熱鬧策州,春花似錦、人聲如沸宫仗。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,676評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)藕夫。三九已至孽糖,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間毅贮,已是汗流浹背办悟。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,829評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留滩褥,地道東北人病蛉。 一個(gè)月前我還...
    沈念sama閱讀 47,743評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像瑰煎,于是被迫代替她去往敵國(guó)和親铺然。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,629評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容