目录

介绍

用例

Class Blob

Blob主要源码分析

Blob构造函数

Blob各个维度大小设置

Blob维度信息获取

Blob计算偏移量

Blob数据存储与读取相关操作

Blob数据操作

Blob源码总结

SyncedMemory

Class SyncedMemory

SyncedMemory源码分析

SyncedMemory构造函数

CPU内存相关操作

cpu_data

mutable_cpu_data

set_cpu_data

GPU内存相关操作

gpu_data

mutable_gpu_data

数据同步

总结

参考资料


介绍

Blob是Caffe的基本存储单元,主要作用是为提供了统一的存储器接口,用于存储和交换数据,比便进行后续的操作。Blob数据结构源码实现主要在caffe\caffe\src\caffe\blob.cpp文件中,头文件在caffe\include\caffe\blob.hpp文件中

Blob内存是一个4维度的数组,从低维度到高以此为 num,channels, height和width,其中height和width分别为图像的高度和宽度,channels表示图像RGB通道,num为相当于一个视频流的第几帧

用例

下面为来自于《21天实战caffe》书中的一个Blob使用用例:

#include <vector>
#include <iostream>
#include <caffe/blob.hpp>using namespace caffe;
using namespace std;int main(void)
{Blob<float> a;cout<<"Size : "<<a.shape_string()<<endl;a.Reshape(1,2,3,4);cout<<"Size : "<<a.shape_string()<<endl;cout<<"count : "<<a.count()<<endl;//cout<<"count : "<<a.num()<<endl;float *p = a.mutable_cpu_data();for(int i =0; i < a.count(); i++){p[i] = i;}cout<<"num : "<<a.num()<<endl;cout<<"channels :"<< a.channels()<<endl;cout<<"height: "<<a.height()<<endl;cout<<"width: "<<a.width()<<endl;for(int u=0; u<a.num();u++){for(int v=0; v<a.channels();v++){for(int w=0;w<a.height();w++){for(int x=0; x<a.width();x++){cout<<"a["<<u<<"]["<<v<<"]["<<w<<"]["<<x<<"]= "<<a.data_at(u,v,w,x)<<endl;}}}}cout<<"ASUM = "<< a.asum_data()<<endl;cout<<"SUMSQ = "<< a.sumsq_data()<<endl;cout<<"offset = "<< a.offset(0,1,2,3)<<endl;return 0;
}

上述例子为blob的基本使用用例

1:首先创建一个Blob<float> a, 数据类型为float的Blob类

2:调用Blob::Reshape()接口,确定各个维度的大小,a.Reshape(1,2,3,4),num,channels, height和width分别为1,2,3,4

Reshape会根据设置的各个维度大小,以及数据类型计算内存所占用的空间,将其空间大小按照一维空间申请,确定内存是在CPU上申请或是在GPU上申请,由于环境没有英伟达GPU(主要是太贵,可以买一个嵌入式的英伟达JSON板子不超过1千大洋玩玩),就没有安装CUDA,其实此时还没有真正的申请内存(后面源码中再介绍,因为此时还没有开始使用内存,只是记录所需要的内存大小,这样做可以防止申请的Blob后面长期没有使用而占用内存情况)

3:float *p = a.mutable_cpu_data();获取到cpu上申请的内存地址,此时才会真正的申请内存,申请的内存将多维转成一维

4:a.count()获取到申请的数目成员相当于num*channels*height*width,因为申请内存时已经转成一维所以可以按照一维遍历到所有的数组成员

5:a.num(), a.channels(),a.height(),a.width()分别为获取到各个维度的大小,可以使用a.data_at(u,v,w,x),方式按照数组方式直接访问到某个元素

6: a.asum_data()为所有成员的和,a.sumsq_data()为所有成员的平方和, a.offset()计算某个元素的偏移,转成一维数组,距离首个元素的个数

Class Blob

在caffe\include\caffe\blob.hpp看到Blob类中的各种方法:

Blob方法类别 Blob中方法 描述
Blob构造函数 Blob() 默认构造函数

explicit Blob

(const int num, const int channels, const int height,  const int width)

带四个参数的构造函数,功能和Reshape一样
explicit Blob(const vector<int>& shape); 支持将4个参数作为vector进行传参
Blob各个维度大小设置

void Reshape

(const int num, const int channels, const int height,  const int width);

定义Blob中数据各个维度的大小
void Reshape(const vector<int>& shape); 定义Blob中数据各个维度的大小,参数为vector形式
void Reshape(const BlobShape& shape); BlobShape参数形式
void ReshapeLike(const Blob& other); 以另外一个Blob为标准,创建一个与其在各个维度大小一样的Blob
Blob维度信息获取 inline string shape_string() 以字符串的形式返回各个维度的大小
inline const vector<int>& shape() 以vector的形式返回各个维度的大小
inline int shape(int index) 返回单一某个维度的大小
inline int num_axes() 获取到Blob数据的维度
inline int count() 获取到Blob数据的元素个数相当于num*channels*height*width
inline int count(int start_axis, int end_axis)

维度范围之内的到元素个数

start_axis<end_axis

inline int count(int start_axis) 从start_axis开始,获取到num_axes()的维度元素个数
inline int CanonicalAxisIndex(int axis_index)

转换坐标轴索引[-N,N)到[0,N)

如果 0 <= index < num_axes(),则返回index

如果-num_axes <= index <= -1,则返回(num_axes() - (-index))

nline int num() 获取到num的维度的元素个数即0维度的大小
inline int channels() 获取channel的维度的元素个数即1维度的大小
inline int height() 获取height的维度的元素个数即2维度的大小
inline int width() 获取width的维度的元素格式即3维度的大小
inline int LegacyShape(int index) 获取到单一维度的大小,此时的index会在函数内部调用CanonicalAxisIndex进行内部转换
Blob计算偏移量

inline int offset

(const int n, const int c = 0, const int h = 0, const int w = 0)

计算某个元素的偏移量
 inline int offset(const vector<int>& indices) 计算某个元素的偏移量
Blob数据存储与读取相关操作 void CopyFrom(const Blob<Dtype>& source, bool copy_diff = false,     bool reshape = false) 从源Blob拷贝到当前Blob中

inline Dtype data_at

(const int n, const int c, const int h,   const int w)

按照4维索引访问其中某个data数据

inline Dtype diff_at

(const int n, const int c, const int h,  const int w)

按照4维索引访问其中某个diff数据
inline Dtype data_at(const vector<int>& index) 按照4维度引访问其中某个data数据,参数为vector
inline Dtype diff_at(const vector<int>& index) 按照4维索引访问其中某个diff数据,参数为vector
inline const shared_ptr<SyncedMemory>& data() 获取到Blob中data数据指针
inline const shared_ptr<SyncedMemory>& diff() 获取到Blob中diff数据指针
const Dtype* cpu_data() 获取到在CPU上申请data数据,并返回其指针
void set_cpu_data(Dtype* data); 设置Blob中的CPU中data数据指针
const Dtype* gpu_data() 获取到在GPU上申请data数据,并返回其指针
void set_gpu_data(Dtype* data); 设置Blob中的GPU中data数据指针
const int* gpu_shape() 获取到gpu上申请的data数据的shape 指针,该shape占用的内存是在GPU上
const Dtype* cpu_diff() 获取到在CPU上申请diff数据,并返回其指针
const Dtype* gpu_diff() 获取到在GPU上申请diff数据,并返回其指针
Dtype* mutable_cpu_data() 获取到在CPU上申请data数据
Dtype* mutable_gpu_data() 获取到在GPU上申请data数据
Dtype* mutable_cpu_diff() 获取到在CPU上申请diff数据
Dtype* mutable_gpu_diff() 获取到在GPU上申请diff数据

void FromProto

(const BlobProto& proto, bool reshape = true);

从BlobProto恢复一个Blob对象,caffe数据保存和读取文件是通过BlobProto工具

void ToProto

(BlobProto* proto, bool write_diff = false) const

将内存中的Blob数据保存到BlobProto中
Blob数据操作 void Update() Blob更新运算,相当与data与diff进行合并计算结果
Dtype asum_data() 计算所有data元素绝对值之和(L1-范数)
Dtype asum_diff() 计算所有diff元素绝对值之和(L1-范数)
Dtype sumsq_data() 计算所有data元素绝对值平方和再开方(L2-范数)
Dtype sumsq_diff() 计算所有diff元素绝对值平方和再开方(L2-范数
void scale_data(Dtype scale_factor) data乘以一个标量
void scale_diff(Dtype scale_factor) diff乘以一个标量
void ShareData(const Blob& other) 将其data_指针设置为other的data指针,相当与共享指针,经常用于Layer的前向传播,对外界来说为简单的copy,世界上只是简单修改内存指向同一块内存
void ShareDiff(const Blob& other); 将其diff_指针设置为other的data指针,相当与共享指针,经常用于Layer的前向传播,对外界来说为简单的copy,世界上只是简单修改内存指向同一块内存

bool ShapeEquals(const BlobProto& other);

判断两个Blob各个维度大小是否相等
Blob类中的变量 shared_ptr<SyncedMemory> data_ data_数据指针
shared_ptr<SyncedMemory> diff_ diff_数据指针
shared_ptr<SyncedMemory> shape_data_ shape_data_内存指针
vector<int> shape_ Blob的各个维度大小
int count_ 元素个数相当于num*channels*height*width
int capacity_ 存放Blob容器的容量信息

Blob主要源码分析

上述列表中只是罗列出了Blob中相关的API以及类中的相关变量,下面来过一下Blob中的几个主要API源码

Blob构造函数

先看下Blob(const int num, const int channels, const int height,  const int width)接口源码

该函数首先会将capacity_初始化为0,防止变量未初始化,之前用的脏值对造成影响,最后调用Reshape函数,可以看出带4维参数的构建函数,其实最后还是调用的Reshape函数,进一步分析Reshape函数源码

可以看到该函数首先将4个参数封装成vector,最终调用Reshape()vector参数,个人觉得数据来回转换,这块代码效率反而降低,因为在Reshape内还是需要把vector进行解析。

下面来看真正的Reshape主角了:

1:该函数首先对shape的维度进行检查,CHECK_LE为googlelog提供的用于参数检查的方法,检查不通过会产生一个断言,抛出异常,对外提供的函数入参一般都需要先进行参数合法性检查


#define CHECK_EQ(val1, val2) CHECK_OP(_EQ, ==,val1, val2)    //相当于assert(val1 == val2)  release下可用,如果检测为true,则返回NULL,否则就会返回一个有明确提示信息的字符串指针,并输出该信息,然后是程序宕掉。以下同理#define CHECK_NE(val1, val2) CHECK_OP(_NE, !=,val1, val2)    //相当于assert(val1 != val2)#define CHECK_LE(val1, val2) CHECK_OP(_LE, <=,val1, val2)    //相当于assert(val1 <= val2)#define CHECK_LT(val1, val2) CHECK_OP(_LT, < ,val1, val2)    // 相当于assert(val1 < val2)#define CHECK_GE(val1, val2) CHECK_OP(_GE, >=,val1, val2)    //相当于assert(val1 >= val2)#define CHECK_GT(val1, val2) CHECK_OP(_GT, > ,val1, val2)    //相当于assert(val1 > val2)

shape的维度必须小于 kMaxBlobAxes,否则会产生一个断言, kMaxBlobAxes定义为一个常量,大小为32

2:将shape_.的vector大小与shape完全一直,即保证维度都是相对,后面用于存储各个维度的大小

3:检查shape_data_维度是否和shape一致,如果不一致重新申请SyncedMemory,再次第一遇到SyncedMemory结构,其实caffe的对申请内存释放等管理是放在SyncedMemory模块中进行处理,SyncedMemory提供了同时管理CPU和GPU的内存,并提供了CPU和GPU内存之间同步功能,所以才起名为SynceMemory,不仅仅是只提供申请和释放内存的功能,还提供了内存同步功能,后面再专门描述SyncedMemory,为了不打断学习Blob进程,再次做个疑问,适合而止,否则将会陷入一直追求细节的境地,得不到全局观。再次的功能就只记住shape_data_内存不够,就重新new SyncedMemory一个对象

4:获取到CPU data数据指针,其实该步骤调用了SyncedMemory的mutable_cpu_data函数,再次先进行打住,就认为是获取到在CPU上申请到的shape_data数据指针,至于再什么时候申请的?是在new SyncedMemory时申请的(一般不进一步看源码都会认为内容是在第3步骤申请,姑且就先这样认为)

int* shape_data = static_cast<int*>(shape_data_->mutable_cpu_data());

5:这一步就是解析出vector每一个维度的大小数据(是不是觉得前面又封装后面再解析比较麻烦)

  • 首先检查每一个维度的数据大小CHECK_GE(shape[i], 0),大小不能为0,否则会有断言;
  • count_ *= shape[i] 用于计算该维度的所有元素数量。
  • 对每维度的大小进行检查CHECK_LE(shape[i], INT_MAX / count_) ,不能超过整型最大值,以防止溢出(是不是觉得,大神写的代码考虑比较全面,考虑到各种异常),
  • shape_[i] = shape[i] 将每维的大小保存到shape_中,shape_是给Blob类使用的存储各个维度大小数据
  • shape_data[i] = shape[i]将每维大小保存到shape_data中,shape_data是给SyncedMemory类使用存储各个维度大小数据,两者使用范围不一样注意区别

6:如果之前Blob的count大于等于新设的shape,则不用重新申请内存,防止内存重新申请释放造成额外开销

如果 count_ > capacity_ 新设置的shape超过了之前的旧的count则重新申请SyncedMemory,首先将新的count赋值给capacity_,再申请新的SyncedMemory。在次注意到Blob申请了两个对象分别维data_和diff_,并将新的count赋值给capacity_这是caffe的聪明之处,data_是每Layer之前的原数据输入,而diff_一般用作输出或者Layer产生的计算结果,最后可以根据data_来计算出每层的偏差。

除了SyncedMemory结构没有涉及到之外,其blob构造函数主流程就搞通了。可以想象Reshape(const BlobShape& shape)API的实现,道理是一样的就是将BlobShape结构中的维度数据解析出来,然后调用Reshape接口,至于BlobShape结构是什么我们同样留出疑问,不要太过于执着细节,而无法进一步看源码,看源码就应该想剥洋葱一样,一步步剥开真相真相

至此在分析Blob构造函数时,留下了两个疑问:

1: SyncedMemory 类里面都是什么东西,如何申请GPU和CPU内存的以及如何同步管理的,现在一概不知

2:BlobShape是个什么东东也一概不知

这两个疑问不影响进一步分析Blob文件中的代码

Blob各个维度大小设置

在分析Blob的构造函数时,其实已经顺带将Reshape()函数进行了分析,带参数的构造函数最终就是调用的Reshape()函数

但是还有一个函数没有涉及ReshapeLike()函数,主要功能是以另外一个Blob为标准,创建一个与其在各个维度大小一样的Blob,没看源码之前其实也能够猜出差不多,其实就是将other的shape获取到,调用Reshape()函数

看源码和所猜想差不多,先解析shape,然后封装到vector中 调用Reshape()函数

BlobShape数据结构的定义在\src\caffe\proto\caffe.proto文件中

proto文件ProtoBuffer工具用户建立统一的参数描述文件,ProtoBuffer工具是由Google开发的内存与flash,ROM等非易失存储介质交换接口,可以代码自动生成减少其开发人员的开发调试时间,具体细节后面再讨论。

现在我们只知道BlobShape是在\src\caffe\proto\caffe.proto文件定义的,与ProtoBuffer有关,后面细节再讨论,至少我们现在剥掉一层洋葱

Blob维度信息获取

Blob维度信息相关解决较多,但是其实现大部分都比较简单

先看shape_string()函数,该函数主要是将shape信息返回到字符串总,shape信息来源与之前Reshape保存到shape_中

shape_string()代码相对较为简单,再看num(), chanels(), height(), width()等实现

上述几个函数都是调用LegacyShape()接口,每个函数传递的参数不一样,主要是将相关维度信息转换其中第0维为num,第1维为channels,第2维为height, 第3维为width,进一步看LegacyShape()

首先 基本编码规范,函数入参检查必不可少,防止出现越界之类的,其index值得范围可以为[0, 3]或者[-4,-1],二者不能混用,因为其代表了不同维度表示方法,最后调用shape函数,进一步剥洋葱

可以看到最终获取到某一个维度大小信息还是从shape_中获取,但是CanonicalAxisIndex()函数是干啥用的?主要是用来进行index转换,之前有分析到其index可以为[0, 3],但是也可以为[-4,-1], 如果是[-4,-1]那么就需要进行进一步转换

至此大部分的Blob维度信息获取函数都分析到了,还剩余一个函数num_axes和count

Blob计算偏移量

offset就是计算数组中的某个元素距离首个元素有多少个偏移

较为简单,就是逐个计算各个维度,其公式为

((n * channels() + c) * height() + h) * width() + w;

Blob数据存储与读取相关操作

Blob数据存储相关操作较多,是Blob比较关键点,涉及到其内存申请,读取,数据流向等操作,下面来意义剥洋葱将其剥掉

先看下cpu_data和gpu_data源码

cpu_data和gpu_data主要是分别获取到在CPU和GPU上的data 数据指针,直接是调用的SyncedMemory 类中的cpu_data和和gpu_data函数,这两个函数后面在专门介绍,只要记住是获取到data的数据指针即可。

set_cpu_data()和set_gpu_data()两个函数源码

上述两个函数分别为设置cpu和gpu data 内存地址,相当于用户自己申请的一块内存用于设置data 内存,使用之前要注意内存泄漏。

源码看到此基本能够猜想出来,caffe中涉及到真正的内存操作其实都是在SyncedMemory类中,进一步看下其他函数源码:

看到上述几个接口,最后其实都是调用SyncedMemory类中的函数猜想没有错误

进一步分析剩余的两个主要函数分别为FromProto()和ToProto(),分别为从BlobProto中读取数据和保存Blob数据到BlobProto文件中

FromProto()函数主要分为三部

1:默认reshape= true,该步骤主要设置Blob中的数据shape,分别从BlobProto中读取到shape中4个维度信息,并将4个维度的信息封装成vector,最后调用Reshape()函数

2:获取到Blob中所需要数据的shape信息后,从BlobProto中读取信息,

  • 先检查BlobPro中的data数据是在double_data中还是在data中
  • 检查BlobProto中数据大小是否与count_相等,对要读取的数据进行一个初步的合法性判断
  • 循环逐个读取BlobPro中的data的数据到Blob的data中(是否可以优化,进行批量读取)

3:copy BlobProto中的diff数据,其步骤和copy data步骤一样,只是数据存储的地方不一样而已

ToProto函数主要为将Blob中的数据写到BlobProto中:

该函数主要分为两个部分,分别保存shape信息和数据信息

1:首先将BlobProto中的shape信息清除掉,防止未初始化之前脏值的影响,之后通过调用BlobProto中的mutbalbe_shape中的add_dim函数将shape信息添加到BlobProto中

2:保存数据,首先调用clear API,将其BlobProto中的数据区域先清除,此时可以看到默认情况下BlobProto是将Blob数据保存到double中,清除掉double_data和double_diff区域的数据,然后分别将其数据保存到double_data和double_diff中

进一步了解到了BlobProto中提供了一系列的数据保存读取解决,caffe中与具体内存打交道的是SyncedMemory,

1:BlobProto是存储与内存打交道,提供数据从存储与内存之间数据存储与读取服务

2:SyncedMemory是提供CPU和GPU内存操作功能,读取等功能。

对之前遗留的两个问题有了进一步了解

Blob数据操作

Blob数据操作功能,主要提供一些数据操作功能,包含计算所有L1范数和L2范数,数据同步等功能

asum_data()计算data数据中所有绝对值和,其源码如下:

首先获取到的data头部信息(该头部信息在SyncedMemory中),data申请的内存是在CPU上还是GPU上,如果是在CPU上则调用caffe_cpu_asum()接口,如果在gpu上则调用caffe_gpu_asum()接口,这两个函数是CPU的函数时在caffe中的math库中可以找到位于\src\caffe\utilmath_functions.cpp文件中, 而gpu函数时在\src\caffe\util\math_functions.cu文件中使用的时CUDA函数库。

可以看到cpu上的asum函数,直接调用的cblas_sasum(),该函数调用的时BLAS库,BLAS库包含四类数据的计算:单精度实数s,双精度实数d,单精度复数c,双精度复数z。在机器学习中,最常用的是单精度实数 。最常用的BLAS实现由IntelMKL,ATLAS,OpenBlAS等, BLAS的官方链接为http://www.netlib.org/blas/

caffe中GPU版本的 实现直接调用的时CUDA接口,所以caffe只支持英伟达的GPU环境,其他opencl的GPU环境不支持

那么其他函数如sumsq_data和sumsq_diff同样也是调的BLAS和CUDA的库

进一步分析看下caffe_cpu_dot和caffe_gpu_dot函数:

有兴趣的可以继续看下其他几个函数实现, ShapeEquals()判断

Blob源码总结

上述分析过程主要过了下Blob的源码,可以看到Blob也是一个中间层,对存储与内存的操作是通过BlobProto,而对内存的维护是通过SyncedMemory来完成。看过Blob源码之后,发现还是没有具体找到内存是在那个步骤申请与释放的,只是知道是在SyncedMemory完成的,下面来分析一下SyncedMemory的源码

SyncedMemory

SyncedMemory是真正的内存实施单元,主要申请GPU和CPU内存,以及CPU和GPU内存同步功能,位于\src\caffe\syncedmem.cpp中

Class SyncedMemory

SyncedMemory方法类别 SyncedMemory方法 描述
构造函数  SyncedMemory() 默认构造函数
explicit SyncedMemory(size_t size) 带数据的元素个数即count得参数构造函数
析构函数 ~SyncedMemory() 默认析构函数
CPU内存相关操作 cpu_data()

获取CPU data,

1:如果内存是在GPU上,将会在CPU申请内存并进行将数据同步到CPU

2:如果没有内存没有在CPU和GPU上申请过,则在CPU上申请一块内存

set_cpu_data(void* data) 设置CPU内存地址指针
void* mutable_cpu_data()

获取CPU data,

1: 如果内存是在GPU上,将会在CPU申请内存并进行将数据同步到CPU,并且将head标志设为HEAD_AT_CPU

2:如果没有内存没有在CPU和GPU上申请过,则在CPU上申请一块内存

GPU内存相关操作 gpu_data()

获取GPU data,

1:如果内存是在CPU上,将会在GPU申请内存并进行将数据同步到GPU

2:如果没有内存没有在CPU和GPU上申请过,则在GPU上申请一块内存

void set_gpu_data(void* data) 设置GPU内存地址指针
void* mutable_gpu_data()

获取GPU data,

1:如果内存是在CPU上,将会在GPU申请内存并进行将数据同步到GPU,head标准申请为HEAD_AT_GPU

2:如果没有内存没有在CPU和GPU上申请过,则在GPU上申请一块内存

Head和Size相关 SyncedHead head() 获取内存Head标志位

enum SyncedHead { UNINITIALIZED,

HEAD_AT_CPU,

HEAD_AT_GPU,

SYNCED }

内存head标志

UNINITIALIZED:为初始化即未申请内存

HEAD_AT_CPU:申请内存在CPU上

HEAD_AT_GPU:申请内存在GPU上

SYNCED :CPU和GPU之间内存是否同步

size_t size() 获取内存大小
数据同步

void async_gpu_push

(const cudaStream_t& stream)

CPU和GPU内存同步
内部私有方法 void check_device(); 检查设备,是否有GPU
 void to_cpu();

1:如果内存在GPU上,则将GPU上的数据同步到CPU上

2:如果内存还未申请,则在CPU上申请内存

3:其他head状态不做处理

void to_gpu()

1:如果内存在CPU上,则将GPU上的数据同步到GPU上

2:如果内存还未申请,则在CPU上申请内存

3:其他head状态不做处理

SyncedMemory内部变量 void* cpu_ptr_ CPU上内存指针
void* gpu_ptr_ GPU上内存指针
size_t size_ 内存大小
SyncedHead head_ 内存 head_ 标志
bool own_cpu_data_ 是否已经在CPU上申请内存,True已经申请过
bool cpu_malloc_use_cuda_ 在CPU上是否使用CUDA接口申请过内存
bool own_gpu_data_ 是否在GPU上申请内存,True已经申请过
nt device_ CUDA 设备ID

SyncedMemory源码分析

SyncedMemory构造函数

SyncedMemory构造函数相对简单,源码如下:

可以看到SyncedMemory构造函数其实没有什么处理,只有当开启GPU后,对是否有CUD 设备进行检查,并没有申请内存的动作,在Blob分析其源码时可知Blob中的new SyncedMemory动作并没有马上申请内存,那么带着疑问继续向下阅读源码,在哪里申请到内存。

CPU内存相关操作

SyncedMemory中CPU内存和GPU内存相关处理式分开的,首先分析CPU内存相关动作。

cpu_data

cpu_data()函数源码如上图所示,主要有三个步骤:

1:调用chekc_devide()函数,如果打开GPU,则此时需要检查GPU设备,GPU设备是否在线以及如果在线其设备是否和之前的device id 一样

该函数主要式调用CUDA接口,对device id进行检查,对CUDA API想要进一步了解可以查看CUDA官网,后续将会介绍CUDA的编程方法

2:to_cpu函数是真正的处理函数,该函数比较重要是CPU 上内存处理比较重要函数

首先如果启动GPU就对设备进行检查,然后根据head_内存头状态分别进行处理

  • UNINITIALIZED:该状态为内存未初始化状态,即还未申请到内存根据size_大小,重新在CPU上申请内存,并初始化,并将head_标志设置未HEAD_AT_CPU,表明申请的内存是在CPU上,own_cpu_data_设置为true,表明以及拥有了CPU 内存,并将cpu_ptr_设置为申请到的内存地址
  • HEAD_AT_GPU:该状态表明为此时申请的内存是在GPU上,并检查cpu_ptr_是否为空,如果为空表明CPU上没有申请内存,里面申请内存,并将own_cpu_data_设置为true。随后调用caffe_gpu_memcpy()接口将GPU上的数据同步到CPU上,并将head_标志位设为SYNCED,意思是在CPU和GPU都有相同内存大小,并且两者数据已经同步,完全一致
  • HEAD_AT_CPU:表明CPU上已经有了该大小内存不需要申请
  • SYNCED:CPU和GPU都有相同内存大小,并且两者数据已经同步,完全一致

to_cpu()函数主要功能有一下几点:

  • 如果内存在GPU上,CPU内存是否申请,如果没有申请一块,并且将GPU上的数据同步到CPU上
  • 如果内存还未申请,则在CPU上申请内存

至此,找到了真正在CPU上申请内存的地方

3:cpu_data最后将cpu_ptr_内存地址进行返回

mutable_cpu_data

mutable_cpu_data函数源码如下:

该函数的前两步骤和 cpu_data()函数一致,多了head_ = HEAD_AT_CPU标志位设置

与 cpu_data()函数最大的不同就是:当在CPU和GPU上都有申请到内存时,mutable_cpu_data会将head_标志位设置为HEAD_AT_CPU,表明以后处理是以CPU上的内存数据为基准

只有在 cpu_data和mutable_cpu_data函数才有申请CPU内存动作

set_cpu_data

set_cpu_data函数是设置cpu_ptr_内存地址,其源码如下:

  • 首先对device和入参data进行检查, data是否为空
  • 如果之前CPU内存有申请过,则将其内存释放调
  • 将data值赋值给cpu_ptr_,并将head_设置为HEAD_AT_CPU,而own_cpu_data_设置为false,表明该内存不是由SyncedMemory自己申请的,而是由用户来申请的

该函数最大的作用是支持用户自定义申请内存,并将申请到的内存设置到 SyncedMemory中进行管理,体现了caffe的灵活性

GPU内存相关操作

GPU内存操作相关函数处理思路和CPU是一样的,只是实现不一样

gpu_data

gpu_data函数源码如下:

需要在编译时将CPU_ONLY设为0,把GPU功能打开,调用to_gpu函数,和cpu_data的函数差不多

其处理流程和to_cpu流程一样,只是这次的数据流向是从cpu到gpu,不再详细描述。

mutable_gpu_data

mutable_gpu_data源码如下:

与 gpu_data函数相比,就是多了head_ = HEAD_AT_GPU处理

数据同步

专门的数据从cpu同步到GPU函数只有一个async_gpu_push,源码如下:

其主要是调用cuda接口将stream数据同步到gpu上

总结

自此了解 到了Blob和SyncedMemory中的大部分源码处理流程,将Blob这层洋葱剥的差不多,剩余还有一个疑问就是 BlobShape,再后面进行介绍。

参考资料

《21天实战Caffe》

http://caffe.berkeleyvision.org/

剖析Caffe源码之Blob相关推荐

  1. 剖析Caffe源码之Net---Net构造函数

    目录 Net构造函数 读取Prototxt ReadProtoFromTextFile UpgradeNetAsNeeded 设置网络状态 Init函数 FilterNet InsertSplits ...

  2. 剖析Caffe源码之Layer_factory

    在<剖析Caffe源码之Layer>,对Layer代码进行了基本分析,可以知道Layer是所有的其他Layer的基本类,由此来扩展出各个神经网络中所需要的Layer,体现了caffe的可扩 ...

  3. Caffe源码中blob文件分析

    Caffe源码(caffe version commit: 09868ac , date: 2015.08.15)中有一些重要的头文件,这里介绍下include/caffe/blob.hpp文件的内容 ...

  4. 剖析Caffe源码之Net类变量

    在<解析Net的构造函数源码>过程中,可以看到Net类有很多变量,用于存储网络中的各种信息,caffe中类的变量命名规则统一在变量名中加上'_',查看net.hpp代码可以看到使用了很多变 ...

  5. 剖析Caffe源码之Layer

    目录 Layer介绍 Layer分析 LayerParameter Class Layer Layer派生类 Layer源码 Layer构造函数 SetUp函数 Forward函数 Backward函 ...

  6. 剖析Caffe源码之Net(上)---NetParameter参数

    前面几篇文章主要分析了Caffe中的Blob和Layer源码,了解到了Caffe中的参数数据结构文件caffe.proto,掌握了各个Layer是如何注册到Caffe中,下面将分析Net层. 在分析N ...

  7. 剖析Caffe源码之InputLayer

    ImageDataLayer可以完成caffe自动读取图片进行模型训练和推断,但是在实际的应用中一般图像都是通过sensor采集而来,将采集得到的图片送到训练好的模型中进行识别.推断,此时就需要用到I ...

  8. 剖析Caffe源码之Net---NetParameter参数

    前面几篇文章主要分析了Caffe中的Blob和Layer源码,了解到了Caffe中的参数数据结构文件caffe.proto,掌握了各个Layer是如何注册到Caffe中,下面将分析Net层. 在分析N ...

  9. 剖析Caffe源码之ImageDataLayer

    目录 ImageDataLayer参数 Source root_folder new_height.new_width is_color crop_size Prototxt配置 Class Imag ...

最新文章

  1. 想要学习却又无从下手?新手程序员如何自我提升
  2. JAVASCRIPT复制到剪贴板
  3. 利用键盘钩子捕获Windows键盘动作
  4. 舞蹈链(DLX)模板
  5. SAP Cloud for Customer的Calculated field字段
  6. 软件公司管理基本原则
  7. lock字段mysql_MySQL的lock tables和unlock tables的用法(转载)
  8. NOI图论算法:网络流
  9. 没有找到dllregisterserver输入点_Excel教程:框内打的几种输入方法,值得收藏
  10. python可以做仿真吗agent_python agent应用
  11. 美团靠外卖和到店业务赚来的钱
  12. HDMI和DVI的HDCP握手问题分析及其解决方案精粹
  13. Linux之web服务搭建静态网页------综合练习
  14. [Unity 3D] Unity 3D 性能优化(二)
  15. 解决安装C4D打不开的问题 Cinema 4D R25 Mac中英文 支持M1intel处理器(附经验分享——lib4d文件如何导入)
  16. android 手机2k分辨率,2K屏幕手机有哪些 2016六款2K分辨率手机推荐
  17. 漫谈Anchor-based和Anchor-Free
  18. 泛函分析在计算机科学中的应用,泛函分析 - 重庆师范大学数学科学学院.doc
  19. 揭开Java上传下载功能的神秘面纱
  20. 使用mondo制作备份linux的iso文件

热门文章

  1. joa-framework 工作流快速开发框架(jeecg官方工作流版本) 发布
  2. 2019年文章精选,分类整理,顺带聊几句时间观念
  3. stimulsoft入门教程:分层报表(下)
  4. 第二篇 第一章建筑分类和耐火等级检查(二)
  5. Android开发人员应该知道的一些技术
  6. [20180810]exadata--豆腐渣系统的保护神.txt
  7. Google传奇Jeff Dean最新演讲:如何构建未来的机器学习芯片
  8. WindowsMobile应该如何发展?(未完待续)
  9. 这所美国大学研发出了Wi-Fi充电技术
  10. WebStorm 关联 TFS(转)