perflab这节的任务是利用书中知识,来对图像处理中的Rotate和Smooth操作函数进行优化。这次没对上电波,觉得学了一堆屠龙之技。于我个人理解,现在计算机配置比以前高多了,连SWAP分区都几近废弃了,对于一般开发者来讲,代码效率瓶颈首先是架构,其次是算法,最后才是书里教的这些小细节。而且这节也没个具体的分数标准,优化了半天也不知道自己写的算啥水平,缺了前面几节那种攻克难题的成就感。不过也有可能是因为我太菜了 XD

前期准备

这次的开发环境被我迁移到了WSL上,系统版本为ubuntu 18.04 LTS, 使用VSCode remote作为主要编辑器,软件包只装了以下几个:

  1. sudo apt-get install build-essential #安装gcc、make等常用开发工具
  2. sudo apt-get install libc6-dev #安装c++库
  3. sudo apt-get install g++-multilib #让64位机器可以编译32位程序

知识点

主要是CSAPP第五章和第六章所总结的一些小技巧

  • 消除冗余的函数调用。比如避免在for循环里用strlen。
  • 消除不必要的内存引用。比如引入临时变量来把中间结果保存到寄存器里,在全部计算完成后把最终结果存到数组或全局变量里。
  • 循环展开,降低判断语句和增减循环变量的开销。
  • 累积变量和重新组合,提高指令并行性。
  • 功能性风格重写条件操作,即用三元运算符。
  • 提高空间局部性,尽量按照数组在内存里存储的顺序,以1为步长进行读取。
  • 提高时间局部性,一旦在内存里读出一个变量,就尽可能频繁且集中的使用它。

Rotate

对于Rotate操作,我主要优化了以下几点:

  1. 因为高速缓存读操作不命中的惩罚比写操作高,又因为空间局部性原则,所以优先在dst数组上以1为步长遍历。

  2. 为了消除冗余的运算,我们可以对RIDX宏进行拆解,分析可知

  1. dst[dim*dim-dim + i - dim*j] == src[dim*i + j]
  1. 根据讲义里提示所有图片尺寸为32的倍数,又因为CACHE_BLOCK大小为32,所以我们可对原代码进行32路展开
  2. 同样是根据空间局部性原则,尽量使内部循环步长短于外部循环
  1. void rotate(int dim, pixel *src, pixel *dst)
  2. {
  3. // dst = dim*dim-dim + i - dim*j
  4. // src = dim*i + j
  5. int i,j;
  6. dst+=(dim*dim-dim);
  7. for(i=0;i<dim;i+=32){
  8. for(j=0;j<dim;j++){
  9. dst[0]=src[0];
  10. dst[1]=src[dim];
  11. dst[2]=src[2*dim];
  12. dst[3]=src[3*dim];
  13. dst[4]=src[4*dim];
  14. dst[5]=src[5*dim];
  15. dst[6]=src[6*dim];
  16. dst[7]=src[7*dim];
  17. dst[8]=src[8*dim];
  18. dst[9]=src[9*dim];
  19. dst[10]=src[10*dim];
  20. dst[11]=src[11*dim];
  21. dst[12]=src[12*dim];
  22. dst[13]=src[13*dim];
  23. dst[14]=src[14*dim];
  24. dst[15]=src[15*dim];
  25. dst[16]=src[16*dim];
  26. dst[17]=src[17*dim];
  27. dst[18]=src[18*dim];
  28. dst[19]=src[19*dim];
  29. dst[20]=src[20*dim];
  30. dst[21]=src[21*dim];
  31. dst[22]=src[22*dim];
  32. dst[23]=src[23*dim];
  33. dst[24]=src[24*dim];
  34. dst[25]=src[25*dim];
  35. dst[26]=src[26*dim];
  36. dst[27]=src[27*dim];
  37. dst[28]=src[28*dim];
  38. dst[29]=src[29*dim];
  39. dst[30]=src[30*dim];
  40. dst[31]=src[31*dim];
  41. src++; //j++ => src+=1
  42. dst-=dim; //j++ => dim+=-dim
  43. }
  44. //i+=32 => src+=32*dim, then neutralize the effects of for(j)
  45. src+=31*dim;
  46. //i+=32 => dst+=32, then neutralize the effects of for(j)
  47. dst+=dim*dim+32;
  48. }
  49. }

除此之外也尝试过用临时变量代替dim*dim+32,不过收效甚微。以上代码的成绩在16左右

  1. Rotate: Version = naive_rotate: Naive baseline implementation:
  2. Dim 64 128 256 512 1024 Mean
  3. Your CPEs 2.8 4.2 5.3 10.6 11.5
  4. Baseline CPEs 14.7 40.1 46.4 65.9 94.5
  5. Speedup 5.2 9.4 8.8 6.2 8.2 7.4
  6. Rotate: Version = rotate: Current working version:
  7. Dim 64 128 256 512 1024 Mean
  8. Your CPEs 2.7 2.2 2.2 2.7 4.2
  9. Baseline CPEs 14.7 40.1 46.4 65.9 94.5
  10. Speedup 5.4 18.0 21.0 24.8 22.6 16.3

Smooth

对于Smooth操作,我的想法很直白:

  1. avg中有大量的冗余的max和min函数调用,可通过分类讨论四角、四边、中间的边界条件来优化之。
  2. src的每个单元格都被多次读取,利用效率不高,可以通过复用读取的值来减少读取次数。

在以上思想的指导下,我又加了几个辅助函数,最终代码如下:

  1. pixel_sum p_sum[512][512];
  2. static void three_pixel_sum(pixel_sum *sum, pixel a, pixel b, pixel c)
  3. {
  4. sum->red=(int)(a.red+b.red+c.red);
  5. sum->green=(int)(a.green+b.green+c.green);
  6. sum->blue=(int)(a.blue+b.blue+c.blue);
  7. }
  8. static void two_pixel_sum(pixel_sum *sum, pixel a, pixel b){
  9. sum->red=(int)(a.red+b.red);
  10. sum->blue=(int)(a.blue+b.blue);
  11. sum->green=(int)(a.green+b.green);
  12. }
  13. static void add_pixel_sum(pixel_sum *a, pixel_sum b){
  14. a->red+=b.red;
  15. a->green+=b.green;
  16. a->blue+=b.blue;
  17. }
  18. static void sum2pixel(pixel *current_pixel, pixel_sum sum, int num)
  19. {
  20. current_pixel->red = (unsigned short)(sum.red / num);
  21. current_pixel->green = (unsigned short)(sum.green / num);
  22. current_pixel->blue = (unsigned short)(sum.blue / num);
  23. return;
  24. }
  25. void smooth(int dim, pixel *src, pixel *dst)
  26. {
  27. pixel_sum sum;
  28. int r,c;
  29. int dimsubone=dim-1;
  30. //初始化
  31. for(r=0;r<dim;r++){
  32. for(c=0;c<dim;c++){
  33. initialize_pixel_sum(&p_sum[r][c]);
  34. }
  35. }
  36. //计算中间部分
  37. for(r=1;r<dimsubone;r++){
  38. for(c=1;c<dimsubone;c++){
  39. three_pixel_sum(&sum,src[RIDX(r,c-1,dim)],src[RIDX(r,c,dim)],src[RIDX(r,c+1,dim)]);
  40. add_pixel_sum(&p_sum[r-1][c],sum);
  41. add_pixel_sum(&p_sum[r][c],sum);
  42. add_pixel_sum(&p_sum[r+1][c],sum);
  43. }
  44. }
  45. //计算上下两边
  46. for(c=1;c<dimsubone;c++){
  47. three_pixel_sum(&sum,src[RIDX(0,c-1,dim)],src[RIDX(0,c,dim)],src[RIDX(0,c+1,dim)]);
  48. add_pixel_sum(&p_sum[0][c],sum);
  49. add_pixel_sum(&p_sum[1][c],sum);
  50. three_pixel_sum(&sum,src[RIDX(dimsubone,c-1,dim)],src[RIDX(dimsubone,c,dim)],src[RIDX(dimsubone,c+1,dim)]);
  51. add_pixel_sum(&p_sum[dim-2][c],sum);
  52. add_pixel_sum(&p_sum[dimsubone][c],sum);
  53. }
  54. //计算左右两边
  55. for(r=1;r<dimsubone;r++){
  56. two_pixel_sum(&sum,src[RIDX(r,0,dim)],src[RIDX(r,1,dim)]);
  57. add_pixel_sum(&p_sum[r-1][0],sum);
  58. add_pixel_sum(&p_sum[r][0],sum);
  59. add_pixel_sum(&p_sum[r+1][0],sum);
  60. two_pixel_sum(&sum,src[RIDX(r,dim-2,dim)],src[RIDX(r,dimsubone,dim)]);
  61. add_pixel_sum(&p_sum[r-1][dimsubone],sum);
  62. add_pixel_sum(&p_sum[r][dimsubone],sum);
  63. add_pixel_sum(&p_sum[r+1][dimsubone],sum);
  64. }
  65. //计算四角
  66. two_pixel_sum(&sum,src[RIDX(0,0,dim)],src[RIDX(0,1,dim)]);
  67. add_pixel_sum(&p_sum[0][0],sum);
  68. add_pixel_sum(&p_sum[1][0],sum);
  69. two_pixel_sum(&sum,src[RIDX(0,dim-2,dim)],src[RIDX(0,dimsubone,dim)]);
  70. add_pixel_sum(&p_sum[0][dimsubone],sum);
  71. add_pixel_sum(&p_sum[1][dimsubone],sum);
  72. two_pixel_sum(&sum,src[RIDX(dimsubone,0,dim)],src[RIDX(dimsubone,1,dim)]);
  73. add_pixel_sum(&p_sum[dim-2][0],sum);
  74. add_pixel_sum(&p_sum[dimsubone][0],sum);
  75. two_pixel_sum(&sum,src[RIDX(dimsubone,dim-2,dim)],src[RIDX(dimsubone,dimsubone,dim)]);
  76. add_pixel_sum(&p_sum[dim-2][dimsubone],sum);
  77. add_pixel_sum(&p_sum[dimsubone][dimsubone],sum);
  78. //中部有9个相邻点
  79. for(r=1;r<dimsubone;r++){
  80. for(c=1;c<dimsubone;c++){
  81. sum2pixel(&dst[RIDX(r,c,dim)],p_sum[r][c],9);
  82. }
  83. sum2pixel(&dst[RIDX(r,0,dim)],p_sum[r][0],6);
  84. sum2pixel(&dst[RIDX(r,dimsubone,dim)],p_sum[r][dimsubone],6);
  85. }
  86. //四边有6个相邻点
  87. for(c=1;c<dimsubone;c++){
  88. sum2pixel(&dst[RIDX(0,c,dim)],p_sum[0][c],6);
  89. sum2pixel(&dst[RIDX(dimsubone,c,dim)],p_sum[dimsubone][c],6);
  90. }
  91. //四角有4个相邻点
  92. sum2pixel(&dst[RIDX(0,0,dim)],p_sum[0][0],4);
  93. sum2pixel(&dst[RIDX(dimsubone,0,dim)],p_sum[dimsubone][0],4);
  94. sum2pixel(&dst[RIDX(0,dimsubone,dim)],p_sum[0][dimsubone],4);
  95. sum2pixel(&dst[RIDX(dimsubone,dimsubone,dim)],p_sum[dimsubone][dimsubone],4);
  96. }

分数在23左右

  1. Smooth: Version = naive_smooth: Naive baseline implementation:
  2. Dim 32 64 128 256 512 Mean
  3. Your CPEs 52.5 50.2 50.6 52.0 51.7
  4. Baseline CPEs 695.0 698.0 702.0 717.0 722.0
  5. Speedup 13.2 13.9 13.9 13.8 14.0 13.8
  6. Smooth: Version = smooth: Current working version:
  7. Dim 32 64 128 256 512 Mean
  8. Your CPEs 28.8 29.4 29.6 30.6 32.3
  9. Baseline CPEs 695.0 698.0 702.0 717.0 722.0
  10. Speedup 24.1 23.7 23.8 23.4 22.3 23.5

还可以继续利用动态规划思想进行优化,但我懒得搞了。像这种分类情况多,代码量大的题目我确实是不怎么喜欢做。

【CSAPP】Performance Lab 实验笔记的更多相关文章

  1. 【CSAPP】Shell Lab 实验笔记

    shlab这节是要求写个支持任务(job)功能的简易shell,主要考察了linux信号机制的相关内容.难度上如果熟读了<CSAPP>的"异常控制流"一章,应该是可以不 ...

  2. 【CSAPP】Cache Lab 实验笔记

    cachelab这节先让你实现个高速缓存模拟器,再在此基础上对矩阵转置函数进行优化,降低高速缓存不命中次数.我的感受如上一节,实在是不想研究这些犄角旮旯的优化策略了. 前期准备 我实验的时候用到了va ...

  3. 【CSAPP】Attack Lab实验笔记

    attacklab这节玩的是利用一个字符串进行缓冲区溢出漏洞攻击,就小时候想象中黑客干的事儿. 做题的时候好几次感叹这些人的脑洞,"这都可以攻击?还能这么注入?这还可能借力打力?" ...

  4. 【CSAPP】Architecture Lab 实验笔记

    archlab属于第四章的内容.这章讲了处理器体系结构,就CPU是怎样构成的.看到时候跃跃欲试,以为最后实验是真要去造个CPU,配套资料也是一如既往的豪华,合计四十多页的参考手册,一大包的源码和测试程 ...

  5. 【CSAPP】Bomb Lab实验笔记

    bomblab这节搞的是二进制拆弹,可以通俗理解为利用反汇编知识找出程序的六个解锁密码. 早就听闻BOMBLAB的大名,再加上我一直觉得反汇编是个很艰难的工作,开工前我做好了打BOSS心理准备.实际上 ...

  6. 【CSAPP】Data Lab实验笔记

    前天讲到要刚CSAPP,这一刚就是两天半.CSAPP果然够爽,自带完整的说明文档,评判程序,辅助程序.样例直接百万组走起,管饱! datalab讲的是整数和浮点数怎么用二进制表示的,考验的是用基本只用 ...

  7. ChCore Lab3 用户进程和异常处理 实验笔记

    本文为上海交大 ipads 研究所陈海波老师等人所著的<现代操作系统:原理与实现>的课程实验(LAB)的学习笔记的第三篇:用户进程与异常处理.所有章节的笔记可在此处查看:chcore | ...

  8. CSAPP buffer lab记录——IA32版本

    CSAPP buffer lab为深入理解计算机系统(原书第二版)的配套的缓冲区溢出实验,该实验要求利用缓冲区溢出的原理解决5个难度递增的问题,分别为smoke(level 0).fizz(level ...

  9. CSAPP Bomb Lab记录

    记录关于CSAPP 二进制炸弹实验过程 (CSAPP配套教学网站Bomb Lab自学版本,实验地址:http://csapp.cs.cmu.edu/2e/labs.html) (个人体验:对x86汇编 ...

随机推荐

  1. Zookeeper 文件系统 ?

    Zookeeper 提供一个多层级的节点命名空间(节点称为 znode).与文件系统不 同的是,这些节点都可以设置关联的数据,而文件系统中只有文件节点可以存放 数据而目录节点不行. Zookeeper ...

  2. SpringSecurity集成启动报 In the composition of all global method configuration, no annotation support was actually activated 异常

    异常内容: Caused by: java.lang.IllegalStateException: In the composition of all global method configurat ...

  3. js的json序列化和反序列化

    (1)序列化 即js中的Object转化为字符串 1.使用toJSONString var last=obj.toJSONString(); //将JSON对象转化为JSON字符 2.使用string ...

  4. CAS和CAP代表的作用

    CAS(自旋锁):https://www.jianshu.com/p/ab2c8fce878b CAP原则:https://baike.baidu.com/item/CAP原则/5712863?fr= ...

  5. Invalid prop: type check failed for prop "maxlength"

    Invalid prop: type check failed for prop "maxlength", element 框架时,因为想限制文本框的输入长度, maxlength ...

  6. C++ | 虚拟地址空间

    在 x86 32位系统下,进程的虚拟地址空间为 232 (4G)大小,其中在windows系统下4G地址空间中0x00000000-0x7FFFFFFF 是用户地址空间,0x80000000-0xFF ...

  7. PCB中的生产工艺、USB布线、特殊部件、蓝牙天线设计

    PCB中的生产工艺.USB布线.特殊部件.蓝牙天线设计 (2016-07-20 11:43:27) 转载▼     PCB生产中Mark点设计 1.pcb必须在板长边对角线上有一对应整板定位的Mark ...

  8. Living Documentation

    Living Documentation Living documentation in legacy systems Living documentation, which comes from t ...

  9. SQL之创建表

    1.创建表------ (方法一)-------create table Persons(id NUMBER,                                       age NU ...

  10. 从ES6重新认识JavaScript设计模式(三): 建造者模式

    1 什么是建造者模式? 建造者模式(Builder)是将一个复杂对象的构建层与其表示层相互分离,同样的构建过程可采用不同的表示. 建造者模式的特点是分步构建一个复杂的对象,可以用不同组合或顺序建造出不 ...