深度学习 Caffe 内存管理机制理解

786 阅读4分钟

之前在简书的文章,搬迁过来 ^-^
本文是作者原创,如有理解错误,恳请大家指出,如需引用,请注明出处。

#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的一些理解,希望对大家有帮助。