SyncedMemory类简介

最近在阅读caffe源码,代码来自BVLC/caffe,基本是参照网络上比较推荐的 Blob-->Layer-->Net-->Solver 的顺序来分析。其中SyncedMemory类是caffe中底层的结构,负责操作(申请、拷贝等)内存或显存中的数据。

syncedmem.cpp源码

  1. SyncedMemory::SyncedMemory() //构造函数,初始化内部的变量,size为0,指针为空等
  2. : cpu_ptr_(NULL), gpu_ptr_(NULL), size_(0), head_(UNINITIALIZED),
  3. own_cpu_data_(false), cpu_malloc_use_cuda_(false), own_gpu_data_(false) {
  4. #ifndef CPU_ONLY
  5. #ifdef DEBUG
  6. CUDA_CHECK(cudaGetDevice(&device_)); //cudaGetDevice()函数会返回当前被使用的设备
  7. #endif
  8. #endif
  9. }
  10. SyncedMemory::SyncedMemory(size_t size) //构造函数,设置size_的值(不会分配内存)
  11. : cpu_ptr_(NULL), gpu_ptr_(NULL), size_(size), head_(UNINITIALIZED),
  12. own_cpu_data_(false), cpu_malloc_use_cuda_(false), own_gpu_data_(false) {
  13. #ifndef CPU_ONLY
  14. #ifdef DEBUG
  15. CUDA_CHECK(cudaGetDevice(&device_));
  16. #endif
  17. #endif
  18. }
  19. SyncedMemory::~SyncedMemory() { //析构函数
  20. check_device(); //检查gpu设备
  21. if (cpu_ptr_ && own_cpu_data_) { //如果cpu数据的指针不为空并且数据为自身创建的
  22. CaffeFreeHost(cpu_ptr_, cpu_malloc_use_cuda_); //释放数据
  23. }
  24. #ifndef CPU_ONLY
  25. if (gpu_ptr_ && own_gpu_data_) { //同理,gpu数据指针不为空并且数据为自身创建的
  26. CUDA_CHECK(cudaFree(gpu_ptr_)); //释放
  27. }
  28. #endif // CPU_ONLY
  29. }
  30. //将数据转移到cpu中.如果还未创建内存则申请对应大小的内存,
  31. //如果数据只在gpu中则将数据拷至cpu中,如果cpu中已存在则不处理
  32. inline void SyncedMemory::to_cpu() {
  33. check_device();
  34. switch (head_) { //当前数据的状态
  35. case UNINITIALIZED: //未分配状态
  36. CaffeMallocHost(&cpu_ptr_, size_, &cpu_malloc_use_cuda_); //申请内存
  37. caffe_memset(size_, 0, cpu_ptr_); //数据全部设置为0
  38. head_ = HEAD_AT_CPU; //设置状态为数据位于内存中,由cpu处理
  39. own_cpu_data_ = true; //数据由自身申请创建
  40. break;
  41. case HEAD_AT_GPU: //当前数据位于gpu中
  42. #ifndef CPU_ONLY
  43. if (cpu_ptr_ == NULL) {
  44. CaffeMallocHost(&cpu_ptr_, size_, &cpu_malloc_use_cuda_); //如果cpu数据指针为空,则申请内存
  45. own_cpu_data_ = true;
  46. }
  47. caffe_gpu_memcpy(size_, gpu_ptr_, cpu_ptr_); //将gpu_ptr_中的数据复制到cpu_ptr_中,复制size_大小
  48. head_ = SYNCED; //设置状态为已同步(cpu数据与gpu数据拥有相同的数据)
  49. #else
  50. NO_GPU; //数据在gpu中但是不支持gpu,错误
  51. #endif
  52. break;
  53. case HEAD_AT_CPU: //数据已经在cpu中,不进行处理
  54. case SYNCED:
  55. break;
  56. }
  57. }
  58. //同理,将数据转移到gpu中
  59. inline void SyncedMemory::to_gpu() {
  60. check_device();
  61. #ifndef CPU_ONLY
  62. switch (head_) {
  63. case UNINITIALIZED: //未初始化
  64. CUDA_CHECK(cudaMalloc(&gpu_ptr_, size_)); //申请显存
  65. caffe_gpu_memset(size_, 0, gpu_ptr_); //置为0
  66. head_ = HEAD_AT_GPU; //设置状态为gpu
  67. own_gpu_data_ = true;
  68. break;
  69. case HEAD_AT_CPU: //数据位于cpu中
  70. if (gpu_ptr_ == NULL) {
  71. CUDA_CHECK(cudaMalloc(&gpu_ptr_, size_)); //申请显存
  72. own_gpu_data_ = true;
  73. }
  74. caffe_gpu_memcpy(size_, cpu_ptr_, gpu_ptr_); //将数据从cpu_ptr_拷至gpu_ptr_
  75. head_ = SYNCED;
  76. break;
  77. case HEAD_AT_GPU:
  78. case SYNCED:
  79. break;
  80. }
  81. #else
  82. NO_GPU;
  83. #endif
  84. }
  85. //返回cpu数据的指针,指向的数据不可修改
  86. const void* SyncedMemory::cpu_data() {
  87. check_device(); //检查设备是否出错
  88. to_cpu(); //数据转移至cpu中
  89. return (const void*)cpu_ptr_;
  90. }
  91. //将cpu的数据指针设置为data
  92. void SyncedMemory::set_cpu_data(void* data) {
  93. check_device(); //检查
  94. CHECK(data); //非空检查
  95. if (own_cpu_data_) { //自身已经创建了cpu数据,先释放
  96. CaffeFreeHost(cpu_ptr_, cpu_malloc_use_cuda_);
  97. }
  98. cpu_ptr_ = data; //指向data
  99. head_ = HEAD_AT_CPU; //修改状态
  100. own_cpu_data_ = false; //数据并非自身申请创建的,在调用析构函数时,并不会释放cpu_ptr_指向的内存
  101. }
  102. //返回gpu数据的指针,指向的数据不可修改
  103. const void* SyncedMemory::gpu_data() {
  104. check_device();
  105. #ifndef CPU_ONLY
  106. to_gpu(); //转移到gpu中
  107. return (const void*)gpu_ptr_;
  108. #else
  109. NO_GPU;
  110. return NULL;
  111. #endif
  112. }
  113. //设置gpu数据的指针
  114. void SyncedMemory::set_gpu_data(void* data) {
  115. check_device();
  116. #ifndef CPU_ONLY
  117. CHECK(data);
  118. if (own_gpu_data_) { //自身创建的gpu数据,先释放
  119. CUDA_CHECK(cudaFree(gpu_ptr_));
  120. }
  121. gpu_ptr_ = data;
  122. head_ = HEAD_AT_GPU;
  123. own_gpu_data_ = false; //同样设置为false
  124. #else
  125. NO_GPU;
  126. #endif
  127. }
  128. //返回cpu上的数据指针,指向的数据可修改
  129. void* SyncedMemory::mutable_cpu_data() {
  130. check_device();
  131. to_cpu();
  132. head_ = HEAD_AT_CPU;
  133. return cpu_ptr_;
  134. }
  135. //返回gpu上的数据指针,指向的数据可修改
  136. void* SyncedMemory::mutable_gpu_data() {
  137. check_device();
  138. #ifndef CPU_ONLY
  139. to_gpu();
  140. head_ = HEAD_AT_GPU;
  141. return gpu_ptr_;
  142. #else
  143. NO_GPU;
  144. return NULL;
  145. #endif
  146. }
  147. //从cpu中来拷贝数据至gpu,异步拷贝
  148. #ifndef CPU_ONLY
  149. void SyncedMemory::async_gpu_push(const cudaStream_t& stream) {
  150. check_device();
  151. CHECK(head_ == HEAD_AT_CPU); //当前数据应在cpu中
  152. if (gpu_ptr_ == NULL) {
  153. CUDA_CHECK(cudaMalloc(&gpu_ptr_, size_)); //申请gpu显存
  154. own_gpu_data_ = true;
  155. }
  156. const cudaMemcpyKind put = cudaMemcpyHostToDevice; //设置拷贝方向,Host To Device
  157. //Copies data between host and device.异步操作,可能在数据拷贝完成之前函数便返回
  158. //cudaMemcpy()为同步的,数据拷贝完后函数才会返回
  159. CUDA_CHECK(cudaMemcpyAsync(gpu_ptr_, cpu_ptr_, size_, put, stream)); //将cpu_ptr_数据拷贝至gpu_ptr_中
  160. // Assume caller will synchronize on the stream before use
  161. head_ = SYNCED; //共享
  162. }
  163. #endif
  164. void SyncedMemory::check_device() { //检查设备,判断是否出错
  165. #ifndef CPU_ONLY
  166. #ifdef DEBUG
  167. int device;
  168. cudaGetDevice(&device); //返回当前被使用的设备
  169. CHECK(device == device_);
  170. if (gpu_ptr_ && own_gpu_data_) {
  171. cudaPointerAttributes attributes;
  172. CUDA_CHECK(cudaPointerGetAttributes(&attributes, gpu_ptr_)); //返回gpu_ptr_指针的属性到attributes中
  173. CHECK(attributes.device == device_); //检查指针所在的设备与类中保存的设备device_是否一致
  174. }
  175. #endif
  176. #endif
  177. }

syncedmem.hpp

  1. // If CUDA is available and in GPU mode, host memory will be allocated pinned,
  2. // using cudaMallocHost. It avoids dynamic pinning for transfers (DMA).
  3. // The improvement in performance seems negligible in the single GPU case,
  4. // but might be more significant for parallel training. Most importantly,
  5. // it improved stability for large models on many GPUs.
  6. //申请内存
  7. inline void CaffeMallocHost(void** ptr, size_t size, bool* use_cuda) {
  8. #ifndef CPU_ONLY
  9. if (Caffe::mode() == Caffe::GPU) { //gpu模式下
  10. CUDA_CHECK(cudaMallocHost(ptr, size)); //分配锁页内存
  11. *use_cuda = true; //使用了cuda
  12. return;
  13. }
  14. #endif
  15. #ifdef USE_MKL //使用了Intel的Math Kernel Library库
  16. *ptr = mkl_malloc(size ? size:1, 64);
  17. #else
  18. *ptr = malloc(size); //朴实无华的内存创建(分页内存)
  19. #endif
  20. *use_cuda = false; //未使用cuda
  21. CHECK(*ptr) << "host allocation of size " << size << " failed";
  22. }
  23. //释放内存
  24. inline void CaffeFreeHost(void* ptr, bool use_cuda) {
  25. #ifndef CPU_ONLY
  26. if (use_cuda) { //使用了cuda,则使用cuda函数释放对应的内存
  27. CUDA_CHECK(cudaFreeHost(ptr));
  28. return;
  29. }
  30. #endif
  31. #ifdef USE_MKL
  32. mkl_free(ptr);
  33. #else
  34. free(ptr);
  35. #endif
  36. }
  37. /**
  38. * @brief Manages memory allocation and synchronization between the host (CPU)
  39. * and device (GPU).
  40. *
  41. * TODO(dox): more thorough description.
  42. */
  43. class SyncedMemory {
  44. public:
  45. SyncedMemory();
  46. explicit SyncedMemory(size_t size);
  47. ~SyncedMemory();
  48. const void* cpu_data();
  49. void set_cpu_data(void* data);
  50. const void* gpu_data();
  51. void set_gpu_data(void* data);
  52. void* mutable_cpu_data();
  53. void* mutable_gpu_data();
  54. //数据的几种状态,UNINITIALIZED(未初始化,内存或显存还未申请), HEAD_AT_CPU(数据在cpu中),
  55. //HEAD_AT_GPU(数据在gpu中), SYNCED(数据在cpu和gpu中都存在,并且内容相同)
  56. enum SyncedHead { UNINITIALIZED, HEAD_AT_CPU, HEAD_AT_GPU, SYNCED };
  57. SyncedHead head() const { return head_; }
  58. size_t size() const { return size_; }
  59. #ifndef CPU_ONLY
  60. void async_gpu_push(const cudaStream_t& stream);
  61. #endif
  62. private:
  63. void check_device();
  64. void to_cpu(); //数据转移到cpu中
  65. void to_gpu(); //数据转移到gpu中
  66. void* cpu_ptr_; //cpu中的数据指针
  67. void* gpu_ptr_; //gpu中的数据指针
  68. size_t size_; //数据的大小
  69. SyncedHead head_; //数据的状态,共SyncedHead中指示的四种
  70. //cpu中的数据是否有自身创建,还是外部传入的指针?(自身创建自己负责释放,外部传的指针析构时不会释放,由外部决定)
  71. bool own_cpu_data_;
  72. bool cpu_malloc_use_cuda_; //申请cpu数据时是否使用了cuda
  73. bool own_gpu_data_; //同理,gpu中的数据是否由自身创建
  74. int device_; //当前使用的gpu设备
  75. DISABLE_COPY_AND_ASSIGN(SyncedMemory); //禁止类的拷贝或者赋值操作
  76. }; // class SyncedMemory

小结

  1. cpu处理的数据对应内存数据,gpu处理的数据对应显存数据
  2. 单纯创建SyncedMemory类的实例时并不会分配内存或显存,只有在实际需要访问数据的时候(如cpu_data()/mutable_gpu_data()等)时,才会在内部的to_cpu()或to_gpu()函数中分配对应的内存或显存
  3. CaffeMallocHost()函数中使用cudaMallocHost()分配的锁页内存,这种内存可被gpu设备直接访问,读写速度比普通的分页内存(malloc申请)要快。关于CUDA的各种函数可参考官方提供的手册。

Caffe的源码笔者是第一次阅读,一边阅读一边记录,对代码的理解和分析可能会存在错误或遗漏,希望各位读者批评指正,谢谢支持!

参考

https://docs.nvidia.com/pdf/CUDA_Runtime_API.pdf

https://www.zhihu.com/question/27982282

Caffe源码-SyncedMemory类的更多相关文章

  1. Caffe源码-Blob类

    Blob类简介 Blob是caffe中的数据传递的一个基本类,网络各层的输入输出数据以及网络层中的可学习参数(learnable parameters,如卷积层的权重和偏置参数)都是Blob类型.Bl ...

  2. Caffe源码-Solver类

    Solver类简介 Net类中实现了网络的前向/反向计算和参数更新,而Solver类中则是对此进行进一步封装,包含可用于逐次训练网络的Step()函数,和用于求解网络的优化解的Solve()函数,同时 ...

  3. Caffe源码-SGDSolver类

    SGDSolver类简介 Solver类用于网络参数的更新,而SGDSolver类实现了优化方法中的随机梯度下降法(stochastic gradient descent),此外还具备缩放.正则化梯度 ...

  4. Caffe源码-Net类(下)

    net.cpp部分源码 // 接着上一篇博客的介绍,此部分为Net类中前向反向计算函数,以及一些与HDF5文件或proto文件相互转换的函数. template <typename Dtype& ...

  5. Caffe源码-Net类(上)

    Net类简介 Net类主要处理各个Layer之间的输入输出数据和参数数据共享等的关系.由于Net类的代码较多,本次主要介绍网络初始化部分的代码.Net类在初始化的时候将各个Layer的输出blob都统 ...

  6. Caffe源码-Layer类

    Layer类简介 Layer是caffe中搭建网络的基本单元,caffe代码中包含大量Layer基类派生出来的各种各样的层,各自通过虚函数 Forward() 和 Backward() 实现自己的功能 ...

  7. Caffe源码-几种优化算法

    SGD简介 caffe中的SGDSolver类中实现了带动量的梯度下降法,其原理如下,\(lr\)为学习率,\(m\)为动量参数. 计算新的动量:history_data = local_rate * ...

  8. Caffe源码理解2:SyncedMemory CPU和GPU间的数据同步

    目录 写在前面 成员变量的含义及作用 构造与析构 内存同步管理 参考 博客:blog.shinelee.me | 博客园 | CSDN 写在前面 在Caffe源码理解1中介绍了Blob类,其中的数据成 ...

  9. caffe源码阅读

    参考网址:https://www.cnblogs.com/louyihang-loves-baiyan/p/5149628.html 1.caffe代码层次熟悉blob,layer,net,solve ...

随机推荐

  1. Nmap强大在哪之主机发现

    1.概述 博主前段时间刚入坑渗透测试,随着学习的深入,越来越发现Nmap简直无所不能.今天先从主机发现功能入手分析. 2.Nmap主机发现 nmap --help #nmap帮助 3.参数分析 3.1 ...

  2. nslookup命令查找域名

    了解 DNS 域名服务 熟悉使用 nslookup 查找 DNS 服务器上登记的域名,记录几次查询的结果, 及服务器的 ip. 1. 某个子域下的一部分主机的名字- IP 地址对应关系,如 flame ...

  3. 图解 Spring:HTTP 请求的处理流程与机制【1】

    2003 年,老兵哥初到中兴开始研究生实习,Spring 就是那年诞生的,2004 年 3 月发布了 1.0 版本,到现在已经超过 15 年了.从单体式分层架构到云原生微服务架构,它稳坐在 JAVA ...

  4. 数据表与简单java类——多对多映射

    给定一张Meber和Product表,得到如下信息: 1.获取一个用户访问的所有商品信息 2.获取一个商品被哪些用户浏览过的信息 package Mapping_transformation; cla ...

  5. ios中日期处理

  6. 转:Spring配置文件<context:property-placeholder>标签使用漫谈

    <context:property-placeholder>标签提供了一种优雅的外在化参数配置的方式,不过该标签在Spring配置文件中只能存在一份!!! 众所周知,Spring容器是采用 ...

  7. ASP.NET Core 选项模式源码学习Options Configure(一)

    前言 ASP.NET Core 后我们的配置变得更加轻量级了,在ASP.NET Core中,配置模型得到了显著的扩展和增强,应用程序配置可以存储在多环境变量配置中,appsettings.json用户 ...

  8. WARNING: Your kernel does not support swap limit capabilities. Limitation discarded.

    利用vSphere调整各台虚拟机后,重新启动mesos,让其启动docker,并为每个container分配cpu和mem,但每次都有一个TASK_LOST. 查看mesos slave的log,发现 ...

  9. 【已解决】解决IntelliJ IDEA控制台输出中文乱码问题

    IntelliJ IDEA 真的是一款很方便的Java开发工具,但是关于中文乱码这个问题我不得不吐槽,这个编码也弄得这么麻烦干嘛,真想找idea开发者干架,我敢打包票我能在一分钟之内一拳飞过去让他跪下 ...

  10. CodeForces-999A-Mishka and Contest

    Mishka started participating in a programming contest. There are nn problems in the contest. Mishka' ...