1.概述

最近接触了一个问题,如何用c++去实现图像中的maxpooling操作,在网上找了一些资料,发现只有一些简单的实现算法,这些算法实际运行复杂度较高。因此,本文,探究了如何利用c++的多线程(OpenMP)、“一条指令操作多个数据”(SIMD)等方向出发,优化maxpooling操作。

2.  maxpooling

参考博客对Max Pooling的理解_117瓶果粒橙-CSDN博客_maxpooling中的理解,整个图片被不重叠的分割成若干个同样大小的小块(pooling size)。每个小块内只取最大的数字,再舍弃其他节点后,保持原有的平面结构得出 output。

在这篇文章中设定最大池化操作的参数kernel_size=3,stride=2,padding=1(在图像周围填充一格0),那么在输入矩阵src1为:

1

1

2

4

5

6

7

8

3

2

1

0

1

2

3

4

最大池化的结果应该为

6 8
6 8

3.pytorch实现和c++实现

pytorch

c++(版本1)

//求maxpooling操作vector<vector<vector<vector<int>>>> maxpooling(){int batch = src1.size(); int channel = src1[0].size();int height=src1[0][0].size(); int width=src1[0][0][0].size();int out_h = (height-3+2)/2, out_w = (width-3+2)/2;int mod_h = (height-3+2)%2, mod_w =(width-3+2)%2;if (mod_h != 0) out_h++;if (mod_w != 0) out_w++;//先填充vector<vector<vector<vector<int>>>> pad_map;for(int b=0;b<batch;b++){vector<vector<vector<int>>> c_tmp;for(int c=0;c<channel;c++){vector<vector<int>> h_tmp(height+2,vector<int>(width+2,0));for(int h=0;h<height+2;h++){for(int w=0;w<width+2;w++){if(h>=1&&h<height+2-1&&w>=1&&w<width+2-1){h_tmp[h][w]=src1[b][c][h-1][w-1];}}}c_tmp.push_back(h_tmp);}pad_map.push_back(c_tmp);c_tmp.clear();}// cout<<pad_map.size()<<"###"; //求maxpoling后的结果vector<vector<vector<vector<int>>>> pool_out;vector<vector<vector<vector<vector<int>>>>> max_index;for(int b=0;b<batch;b++){vector<vector<vector<int>>> c_tmp;vector<vector<vector<vector<int>>>> c_index;for(int c=0;c<channel;c++){vector<vector<int>> res(out_h, vector<int>(out_w, 0));vector<vector<vector<int>>> res_index;for(int i=0;i<out_h;i++){vector<vector<int>> tmp;for (int j = 0; j < out_w; j++){int start_x = i*2;int start_y = j*2;// vector<int> temp;int max_ele=pad_map[b][c][start_x][start_y];vector<int> m_idx(2,0);for(int ii=0;ii<3;ii++)for (int jj = 0; jj <3; jj++){// temp.push_back(pad_map[b][c][start_y + jj][start_x + ii]);if(pad_map[b][c][start_x + ii][start_y + jj]>max_ele){max_ele=pad_map[b][c][start_x + ii][start_y + jj];m_idx[0]=(start_x + ii);m_idx[1]=(start_y+jj);}}// sort(temp.begin(), temp.end());tmp.push_back(m_idx);res[i][j] = max_ele;}res_index.push_back(tmp);tmp.clear();}c_tmp.push_back(res);c_index.push_back(res_index);res_index.clear();res.clear();}pool_out.push_back(c_tmp);max_index.push_back(c_index);c_index.clear();c_tmp.clear();}argmax_index=max_index;return pool_out;}

从代码中可以看到maxpooling的操作,无非就是就是先填充,然后取滑动窗口中数值的最大值,可以看出来计算maxpooling的操作需要六层循环,batch*channel*out_h*out_w*3*3,可见复杂度挺高的。

4. 代码优化

(1)版本1的代码中存在着大量vector的插入、删除操作(哈哈,还不是很熟悉c++),这些操作消耗了大量时间;

(2)引入OpenMP,利用多线程操作,完成最大值池化,可以参考OpenMP共享内存并行编程详解 - liangliangh - 博客园 (cnblogs.com)

(3)gcc编译器本身存在代码优化,利用-O3操作,对代码进行自动优化;

最终优化的代码C++

//求maxpooling操作vector<vector<vector<vector<vector<int>>>>> maxpooling(vector<vector<vector<vector<int>>>> &res){cout<<clock()<<endl;int batch = src1.size(); int channel = src1[0].size();int height=src1[0][0].size(); int width=src1[0][0][0].size();int out_h = (height-3+2)/2, out_w = (width-3+2)/2;int mod_h = (height-3+2)%2, mod_w =(width-3+2)%2;if (mod_h != 0) out_h++;if (mod_w != 0) out_w++;// cout<<"out size";// cout<<out_h<<" ";// cout<<out_w<<endl;// cout<<clock()<<endl;//先填充// vector<vector<vector<vector<int>>>> pad_map(batch,vector<vector<vector<int>>>(channel,vector<vector<int>>(height+2,vector<int>(width+2,0))));//  #pragma omp parallel num_threads(4)// {//     #pragma omp for//         for(int b=0;b<batch;b++){//             for(int c=0;c<channel;c++){//                 for(int h=0;h<height+2;h++){//                     for(int w=0;w<width+2;w++){//                         if(h>=1&&h<height+2-1&&w>=1&&w<width+2-1){//                             pad_map[b][c][h][w]=src1[b][c][h-1][w-1];//                         }//                     }//                 }//             }//         }// }cout<<clock()<<endl;// cout<<"pad_map";// cout<<pad_map.size()<<" ";// cout<<pad_map[0].size()<<" ";// cout<<pad_map[0][0].size()<<" ";// cout<<pad_map[0][0][0].size()<<endl;//求maxpoling后的结果// vector<vector<vector<vector<int>>>> pool_out;// pool_out.resize(channel);vector<vector<vector<vector<int>>>> pool_out;vector<vector<vector<vector<vector<int>>>>> max_index;max_index.resize(batch);pool_out.resize(batch);#pragma omp parallel forfor(int b=0;b<batch;b++){pool_out[b].resize(channel);max_index[b].resize(channel);for(int c=0;c<channel;c++){pool_out[b][c].resize(out_h);max_index[b][c].resize(out_h);for(int i=0;i<out_h;i++){pool_out[b][c][i].resize(out_w);max_index[b][c][i].resize(out_w);for(int j=0;j<out_w;j++){max_index[b][c][i][j].resize(2);}}}}//vector<vector<vector<vector<int>>>> pool_out(batch,vector<vector<vector<int>>>(channel,vector<vector<int>>(out_h,vector<int>(out_w,0))));//vector<vector<vector<vector<vector<int>>>>> max_index(batch,vector<vector<vector<vector<int>>>>(channel,vector<vector<vector<int>>>(out_h,vector<vector<int>>(out_w,vector<int>(2,0)))));// cout<<"2222"<<endl;    cout<<clock()<<endl; // #pragma omp parallel num_threads(4)// {#pragma omp  parallel forfor(int b=0;b<batch;b++){for(int c=0;c<channel;c++){for(int i=0;i<out_h;i++){for (int j = 0; j < out_w; j++){int max_ele=INT_LEAST32_MIN;max_index[b][c][i][j][0]=(i*2);max_index[b][c][i][j][1]=( j*2);for(int ii=0;ii<3;ii++)for (int jj = 0; jj <3; jj++){int tmp_x=i*2+ii;int tmp_y=j*2+jj;if(tmp_x==0 || tmp_x==height+1 || tmp_y==0 || tmp_y==width+1){continue;}if(src1[b][c][tmp_x-1][tmp_y-1]>max_ele){max_ele=src1[b][c][tmp_x-1][tmp_y-1];max_index[b][c][i][j][0]=(tmp_x);max_index[b][c][i][j][1]=(tmp_y);}}pool_out[b][c][i][j]=max_ele;}}    }}// }cout<<clock()<<endl;// cout<<"#####"<<endl;// argmax_index=max_index;cout<<clock()<<endl;res=pool_out;return max_index;}

5.实际测试的时间消耗

在设置输入矩阵的维度为[32,64,112,112]

代码 时间消耗(s)
版本1

10.69

+(优化vector的使用) 4.67
+openMp 3.62
+gcc本身指令优化(O3) 0.41

6.SMID

在maxpooling上,暂时没想到比较好的单条指令多数据并行的方式,因此在元素求和上进行了一些尝试。

c++代码

 vector<vector<vector<vector<int>>>> add(vector<vector<vector<vector<int>>>> tensor1,vector<vector<vector<vector<int>>>> tensor2){int batch_1 = tensor1.size(); int channel_1 = tensor1[0].size();int height_1=tensor1[0][0].size(); int width_1=tensor1[0][0][0].size();int batch_2 = tensor2.size(); int channel_2 = tensor2[0].size();int height_2=tensor2[0][0].size(); int width_2=tensor2[0][0][0].size();int batch_3=max(batch_1,batch_2);int channel_3=max(channel_1,channel_2);int height_3=max(height_1,height_2);int width_3=max(width_1,width_2);vector<vector<vector<vector<int>>>> add_res (batch_3,vector<vector<vector<int>>>(channel_3,vector<vector<int>>(height_3,vector<int>(width_3,0))));if(batch_1!=batch_2&& batch_1!=1&&batch_2!=1){cout<<"batch不一致"<<endl;return add_res;}if(channel_1!=channel_2&& channel_1!=1&&channel_2!=1){cout<<"channel不一致"<<endl;return add_res;}if(height_1!=height_2&&height_1!=1&&height_2!=1){cout<<"height不一致"<<endl;return add_res;}if(width_1!=width_2&&width_1!=1&&width_2!=1){cout<<"width不一致"<<endl;return add_res;}// #pragma omp parallel num_threads(4){#pragma omp forfor(int b=0;b<batch_3;b++){int b1=min(b,batch_1-1);int b2=min(b,batch_2-1);for(int c=0;c<channel_3;c++){int c1=min(c,channel_1-1);int c2=min(c,channel_2-1);for(int h=0;h<height_3;h++){int h1=min(h,height_1-1);int h2=min(h,height_2-1);if(width_1==1&& width_2==1){add_res[b][c][h][0]=tensor1[b1][c1][h1][0]+tensor2[b2][c2][h2][0];}else{int w_int=width_3/4;int w_res=width_3%4;for(int w=0;w<w_int;w++){__m128i add;if(width_1==1){__m128i xx1 = _mm_setr_epi32(tensor1[b1][c1][h1][0],tensor1[b1][c1][h1][0],tensor1[b1][c1][h1][0],tensor1[b1][c1][h1][0]);__m128i xx2 = _mm_setr_epi32(tensor2[b2][c2][h2][0+4*w],tensor2[b2][c2][h2][1+4*w],tensor2[b2][c2][h2][2+4*w],tensor2[b2][c2][h2][3+4*w]);add = _mm_add_epi32(xx1, xx2);_mm_storeu_si128((__m128i*)(&add_res[b][c][h][w*4]), add);}else if(width_2==1){__m128i xx1 = _mm_setr_epi32(tensor1[b1][c1][h1][0+4*w],tensor1[b1][c1][h1][1+4*w],tensor1[b1][c1][h1][2+4*w],tensor1[b1][c1][h1][3+4*w]);__m128i xx2 = _mm_setr_epi32(tensor2[b2][c2][h2][0],tensor2[b2][c2][h2][0],tensor2[b2][c2][h2][0],tensor2[b2][c2][h2][0]);add = _mm_add_epi32(xx1, xx2);_mm_storeu_si128((__m128i*)(&add_res[b][c][h][w*4]), add);}else{__m128i xx1 = _mm_setr_epi32(tensor1[b1][c1][h1][0+4*w],tensor1[b1][c1][h1][1+4*w],tensor1[b1][c1][h1][2+4*w],tensor1[b1][c1][h1][3+4*w]);__m128i xx2 = _mm_setr_epi32(tensor2[b2][c2][h2][0+4*w],tensor2[b2][c2][h2][1+4*w],tensor2[b2][c2][h2][2+4*w],tensor2[b2][c2][h2][3+4*w]);add = _mm_add_epi32(xx1, xx2);_mm_storeu_si128((__m128i*)(&add_res[b][c][h][w*4]), add);}}if(w_res>0){for(int w_r=0;w_r<w_res;w_r++){int w1=w_int*4+w_r;int w2=w_int*4+w_r;if(width_1==1){add_res[b][c][h][w_int*4+w_r]=tensor1[b1][c1][h1][0]+tensor2[b2][c2][h2][w2];}else if(width_2==1){add_res[b][c][h][w_int*4+w_r]=tensor1[b1][c1][h1][w1]+tensor2[b2][c2][h2][0];}else{add_res[b][c][h][w_int*4+w_r]=tensor1[b1][c1][h1][w1]+tensor2[b2][c2][h2][w2];}}}}// for(int w=0;w<width_3;w++){//     add_res[b][c][h][w]=tensor1[min(b,batch_1-1)][min(c,channel_1-1)][min(h,height_1-1)][min(w,width_1-1)]+tensor2[min(b,batch_2-1)][min(c,channel_2-1)][min(h,height_2-1)][min(w,width_2-1)];// }}}}}return add_res;}

时间消耗的对比(矩阵维度为(32,1,64,64))

代码 时间消耗(s)
版本1 0.81
+优化vector使用 0.47
+openMP 0.31
+SIMD 0.24
+gcc O3优化 0.11

c++实现maxpooling+利用OpenMP、SIMD优化代码相关推荐

  1. MAT之PSO:利用PSO算法优化二元函数,寻找最优个体适应度

    MAT之PSO:利用PSO算法优化二元函数,寻找最优个体适应度 目录 实现结果 设计代码 实现结果 设计代码 figure [x,y] = meshgrid(-5:0.1:5,-5:0.1:5); z ...

  2. 利用对象池优化数据库操作

    简介:这是利用对象池优化数据库操作的详细页面,介绍了和asp.net,.Net,创建,对象池,示例有关的知识,要查看更多相关信息,请点击此处 说到对象池,大家都不陌生.很多人都实现过,网上的代码也满天 ...

  3. 使用pickle模块序列化数据,优化代码

    使用pickle模块序列化数据,优化代码 pickle是Python标准库中的一个二进制序列化和反序列化库. 可以以二进制的形式将数据持久化保存到磁盘文件中.可以将数据和代码分离,提高代码可读性和优雅 ...

  4. BUAA-2021春-数据结构-综合作业-文本摘要生成(Hash实现 + SIMD优化 终测最速)

    题目内容 问题描述 在自然语言文本处理中,有一种分析文本.自动抽取文本主题思想的方法(通常用于文本摘要生成),其方法如下: 1.        首先分析文本中非停用词(stop-word)的出现频度: ...

  5. java优化代码常见套路

    目录 程序员的痛点(烂代码) 该如何优化代码 前台后台两次md5加盐加密 JSR303和全局异常处理 Redis通用的key生成策略和通用的RedisService方法 程序猿的必读书籍 程序员的痛点 ...

  6. 利用OpenMP加速拉伸图像操作

    前面的博客<OpenCV拉伸图像>介绍了如何利用OpenCV的现成函数实现图像的透视变换.本文受到了<http://blog.csdn.net/xiaowei_cqu/article ...

  7. 利用DelayLoad来优化应用程序的性能.拦截API.

    翻译 <Under the hood -by Matt Pietrek >  源文件 http://www.microsoft.com/msj/0200/hood/hood0200.asp ...

  8. 不要写完代码就束之高阁,适当地优化代码结构,能够为以后的开发带来许多方便,这《重构:改善既有代码的设计》就向你介绍了这方面的技巧,说得非常详细。...

    "不要写完代码就束之高阁,适当地优化代码结构,能够为以后的开发带来许多方便,这<重构:改善既有代码的设计>就向你介绍了这方面的技巧,说得非常详细." "程序几 ...

  9. TVM yolov3优化代码修改(编译运行OK)

    TVM yolov3优化代码修改(编译运行OK) yolov3_quantize_sample.py 附https://github.com/makihiro/tvm_yolov3_sample代码: ...

最新文章

  1. eventEmitter3源码分析与学习
  2. loadrunner 更新中......
  3. 【Android】Looper消息分发(msg.target.dispatchMessage), Handler消息处理(消息回调/外部回调/自身回调)
  4. 结合前段修改mysql表数据_jquery实现点击文字可编辑并修改保存至数据库
  5. 【C语言进阶深度学习记录】二十三 数组的本质分析
  6. !!!随机数生成!!
  7. win10恢复出厂设置_电脑Win10系统恢复出厂设置
  8. 什么是Hystrix,Hystrix简单概述
  9. Linux突然无法使用,是内存不足的问题
  10. 《PIC微控制器项目设计:C语言》一导读
  11. 大学生简单个人静态HTML网页设计作品 DIV布局个人介绍网页模板代码 DW学生个人网站制作成品下载
  12. 7.26 1004度度熊的午饭时光 百度之星题解
  13. char[]和char* 输出长度不同
  14. linux下玩三国志游戏,三国志威力无双手游官网版
  15. RocketMQ独孤九剑-总纲
  16. 华为U8150(IDEOS)手机USB驱动安装
  17. PCB板元器件视觉检测系统解决方案
  18. [Realtek sdk3.4.14b] RTL8197FH-VG设备启动之后,2.4G WiFi始终工作在20M 11g模式问题处理
  19. C# RadioButton: 单选按钮控件
  20. VMware workstation pro虚拟机以及linux操作系统的安装

热门文章

  1. 亮眼财报业绩背后,难掩富途控股估值陷阱
  2. EXCEL之REPT(),让数据更直观!
  3. 微信 版本android 7.0,安卓微信7.0新版对比旧版详细体验
  4. 王见:手机创业很简单,去县城获取粉丝更简单!
  5. SQLite源码编译教程
  6. 常见面试题及解答|计算机网络
  7. 【Debug】matlab中APP Designer,legend图例线条颜色一样
  8. SAP客户主数据相关表
  9. 难忘2017年5月20日的那次雪崩
  10. CTFshow - 七夕杯复现