SyncedMemory类主要负责在主机(CPU)和设备(GPU)之间管理内存分配和数据同步工作,封装了CPU和GPU之间的数据交互操作。
补充一点GPU的相关知识:
对CUDA架构而言,主机端的内存被分为两种,一种是可分页内存(pageable memroy)和页锁定内存(page-lock或 pinned)。可分页内存是由操作系统API malloc()在主机上分配的,页锁定内存是由CUDA函数cudaHostAlloc()在主机内存上分配的,页锁定内存的重要属性是主机的操作系统将不会对这块内存进行分页和交换操作,确保该内存始终驻留在物理内存中。
GPU知道页锁定内存的物理地址,可以通过“直接内存访问(Direct Memory Access,DMA)”技术直接在主机和GPU之间复制数据,速率更快。由于每个页锁定内存都需要分配物理内存,并且这些内存不能交换到磁盘上,所以页锁定内存比使用标准malloc()分配的可分页内存更消耗内存空间。
syncedmen.hpp文件注释:
#ifndef CAFFE_SYNCEDMEM_HPP_
#define CAFFE_SYNCEDMEM_HPP_
#include <cstdlib>
#include "caffe/common.hpp"
//SyncedMemory类也是在caffe命名空间下定义的,主要在Blob类中被调用
namespace caffe {
/*
如果配置为使用GPU,并且GPU可用,则会在主机上分配页锁定内存(Pinned Memory),
与之对应的另一种内存是可分页内存,由主机负责分配和管理,不能由GPU通过地址直接访问
cudaMallocHost函数提供了直接访问主机内存功能(DMA),速率更快
*/
// If CUDA is available and in GPU mode, host memory will be allocated pinned,
// using cudaMallocHost. It avoids dynamic pinning for transfers (DMA).
// The improvement in performance seems negligible in the single GPU case,
// but might be more significant for parallel training. Most importantly,
// it improved stability for large models on many GPUs.
/*
CaffeMallocHost函数是对CUDA中cudaMallocHost函数的封装,用于在主机中分配页锁定内存
函数的参数分别是内存地址指针,大小以及是否使用CUDA的bool量
*/
inline void CaffeMallocHost(void** ptr, size_t size, bool* use_cuda) {
#ifndef CPU_ONLY
//如果没有配置只使用CPU,并且显明配置使用GPU
if (Caffe::mode() == Caffe::GPU) {
//cudaMallocHost函数用于分配页锁定内存,参数是内存在主机上的地址指针和大小
CUDA_CHECK(cudaMallocHost(ptr, size)); //检查CUDA操作返回的异常信息码
*use_cuda = true;
return;
}
#endif
*ptr = malloc(size); //如果定义只使用CPU,则通过C中malloc在主机上分配内存
*use_cuda = false;
//检查是否有内存分配异常信息并输出
CHECK(*ptr) << "host allocation of size " << size << " failed";
}
//caffe中定义的主机内存(包括页锁定内存和可分页内存)释放函数,定义为内联函数
inline void CaffeFreeHost(void* ptr, bool use_cuda) {
#ifndef CPU_ONLY
if (use_cuda) {
CUDA_CHECK(cudaFreeHost(ptr)); //页锁定内存通过CUDA函数cudaFreeHost释放
return;
}
#endif
free(ptr); //主机普通内存通过free释放
}
/**
* @brief Manages memory allocation and synchronization between the host (CPU)
* and device (GPU).
*
* TODO(dox): more thorough description.
*/
//SyncedMemory类完成数据在CPU和GPU间的内存管理和数据同步工作
class SyncedMemory {
public:
//默认的无参构造函数,默认数据状态设置为UNINITIALIZED,初始使用系统默认的GPU设备
SyncedMemory()
: 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关键字定义的一个显式构造函数,带一个size大小参数
explicit SyncedMemory(size_t size)
: 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) {}
//析构函数,调用CaffeFreeHost或者cudaFree完成内存释放工作
~SyncedMemory();
//获取只读的CPU数据指针,在内部调用了to_CPU函数
const void* cpu_data();
//设置CPU内存数据,如果own_cpu_data有效,则会先释放内存,并把内存地址指向新的data数据
void set_cpu_data(void* data);
//获取只读的GPU数据指针,在内部调用了to_CPU函数
const void* gpu_data();
//设置GPU内存数据,如果own_gpu_data有效,则会先释放内存,并把内存地址指向新的data数据
void set_gpu_data(void* data);
//获取可以修改的CPU内存数据指针
void* mutable_cpu_data();
//获取可以修改的GPU内存数据指针
void* mutable_gpu_data();
//枚举类型,定义了4中数据存放状态,分别是未初始化,存放在CPU,存放在GPU和数据已经同步
enum SyncedHead { UNINITIALIZED, HEAD_AT_CPU, HEAD_AT_GPU, SYNCED };
//获取数据的存放状态
SyncedHead head() { return head_; }
//获取数据大小,以字节为单位
size_t size() { return size_; }
#ifndef CPU_ONLY
//如果没有定义只使用CPU,则异步推送数据到GPU上,完成数据传输之后设置数据状态为已同步
void async_gpu_push(const cudaStream_t& stream);
#endif
private:
void to_cpu(); //实现把数据存放在cpu功能
void to_gpu(); //实现把数据存放在gpu功能
void* cpu_ptr_; //指向cpu上数据的指针
void* gpu_ptr_; //指向gpu上数据的指针
size_t size_; //数据大小,以字节为单位
SyncedHead head_; //数据存放状态
bool own_cpu_data_; //标识是否已经分配cpu内存
bool cpu_malloc_use_cuda_; //标识是否通过cuda分配了主机内存, 即主机内存是否是页锁定内存
bool own_gpu_data_; //标识是否已经分配了gpu内存
int gpu_device_; //gpu设备编号
//禁止使用SyncedMemory类的拷贝和赋值操作
DISABLE_COPY_AND_ASSIGN(SyncedMemory);
}; // class SyncedMemory
} // namespace caffe
#endif // CAFFE_SYNCEDMEM_HPP_
总的来说,SyncedMemory类封装了在主机和设备上申请数据内存,互相之间通信,获取主机和设备上的内存数据指针,主机和设备的数据同步等功能。