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

前期准备

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

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

知识点

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

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

Rotate

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

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

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

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

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

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

Smooth

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

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

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

pixel_sum p_sum[512][512];
static void three_pixel_sum(pixel_sum *sum, pixel a, pixel b, pixel c)
{
sum->red=(int)(a.red+b.red+c.red);
sum->green=(int)(a.green+b.green+c.green);
sum->blue=(int)(a.blue+b.blue+c.blue);
}
static void two_pixel_sum(pixel_sum *sum, pixel a, pixel b){
sum->red=(int)(a.red+b.red);
sum->blue=(int)(a.blue+b.blue);
sum->green=(int)(a.green+b.green);
}
static void add_pixel_sum(pixel_sum *a, pixel_sum b){
a->red+=b.red;
a->green+=b.green;
a->blue+=b.blue;
}
static void sum2pixel(pixel *current_pixel, pixel_sum sum, int num)
{
current_pixel->red = (unsigned short)(sum.red / num);
current_pixel->green = (unsigned short)(sum.green / num);
current_pixel->blue = (unsigned short)(sum.blue / num);
return;
}
void smooth(int dim, pixel *src, pixel *dst)
{
pixel_sum sum;
int r,c;
int dimsubone=dim-1;
//初始化
for(r=0;r<dim;r++){
for(c=0;c<dim;c++){
initialize_pixel_sum(&p_sum[r][c]);
}
}
//计算中间部分
for(r=1;r<dimsubone;r++){
for(c=1;c<dimsubone;c++){
three_pixel_sum(&sum,src[RIDX(r,c-1,dim)],src[RIDX(r,c,dim)],src[RIDX(r,c+1,dim)]);
add_pixel_sum(&p_sum[r-1][c],sum);
add_pixel_sum(&p_sum[r][c],sum);
add_pixel_sum(&p_sum[r+1][c],sum);
}
}
//计算上下两边
for(c=1;c<dimsubone;c++){
three_pixel_sum(&sum,src[RIDX(0,c-1,dim)],src[RIDX(0,c,dim)],src[RIDX(0,c+1,dim)]);
add_pixel_sum(&p_sum[0][c],sum);
add_pixel_sum(&p_sum[1][c],sum);
three_pixel_sum(&sum,src[RIDX(dimsubone,c-1,dim)],src[RIDX(dimsubone,c,dim)],src[RIDX(dimsubone,c+1,dim)]);
add_pixel_sum(&p_sum[dim-2][c],sum);
add_pixel_sum(&p_sum[dimsubone][c],sum);
}
//计算左右两边
for(r=1;r<dimsubone;r++){
two_pixel_sum(&sum,src[RIDX(r,0,dim)],src[RIDX(r,1,dim)]);
add_pixel_sum(&p_sum[r-1][0],sum);
add_pixel_sum(&p_sum[r][0],sum);
add_pixel_sum(&p_sum[r+1][0],sum);
two_pixel_sum(&sum,src[RIDX(r,dim-2,dim)],src[RIDX(r,dimsubone,dim)]);
add_pixel_sum(&p_sum[r-1][dimsubone],sum);
add_pixel_sum(&p_sum[r][dimsubone],sum);
add_pixel_sum(&p_sum[r+1][dimsubone],sum);
}
//计算四角
two_pixel_sum(&sum,src[RIDX(0,0,dim)],src[RIDX(0,1,dim)]);
add_pixel_sum(&p_sum[0][0],sum);
add_pixel_sum(&p_sum[1][0],sum);
two_pixel_sum(&sum,src[RIDX(0,dim-2,dim)],src[RIDX(0,dimsubone,dim)]);
add_pixel_sum(&p_sum[0][dimsubone],sum);
add_pixel_sum(&p_sum[1][dimsubone],sum);
two_pixel_sum(&sum,src[RIDX(dimsubone,0,dim)],src[RIDX(dimsubone,1,dim)]);
add_pixel_sum(&p_sum[dim-2][0],sum);
add_pixel_sum(&p_sum[dimsubone][0],sum);
two_pixel_sum(&sum,src[RIDX(dimsubone,dim-2,dim)],src[RIDX(dimsubone,dimsubone,dim)]);
add_pixel_sum(&p_sum[dim-2][dimsubone],sum);
add_pixel_sum(&p_sum[dimsubone][dimsubone],sum);
//中部有9个相邻点
for(r=1;r<dimsubone;r++){
for(c=1;c<dimsubone;c++){
sum2pixel(&dst[RIDX(r,c,dim)],p_sum[r][c],9);
}
sum2pixel(&dst[RIDX(r,0,dim)],p_sum[r][0],6);
sum2pixel(&dst[RIDX(r,dimsubone,dim)],p_sum[r][dimsubone],6);
}
//四边有6个相邻点
for(c=1;c<dimsubone;c++){
sum2pixel(&dst[RIDX(0,c,dim)],p_sum[0][c],6);
sum2pixel(&dst[RIDX(dimsubone,c,dim)],p_sum[dimsubone][c],6);
}
//四角有4个相邻点
sum2pixel(&dst[RIDX(0,0,dim)],p_sum[0][0],4);
sum2pixel(&dst[RIDX(dimsubone,0,dim)],p_sum[dimsubone][0],4);
sum2pixel(&dst[RIDX(0,dimsubone,dim)],p_sum[0][dimsubone],4);
sum2pixel(&dst[RIDX(dimsubone,dimsubone,dim)],p_sum[dimsubone][dimsubone],4); }

分数在23左右

Smooth: Version = naive_smooth: Naive baseline implementation:
Dim 32 64 128 256 512 Mean
Your CPEs 52.5 50.2 50.6 52.0 51.7
Baseline CPEs 695.0 698.0 702.0 717.0 722.0
Speedup 13.2 13.9 13.9 13.8 14.0 13.8 Smooth: Version = smooth: Current working version:
Dim 32 64 128 256 512 Mean
Your CPEs 28.8 29.4 29.6 30.6 32.3
Baseline CPEs 695.0 698.0 702.0 717.0 722.0
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. Spring Boot 的核心注解是哪个?它主要由哪几个注解组成的?

    启动类上面的注解是@SpringBootApplication,它也是 Spring Boot 的核心注解,主要组合包含了以下 3 个注解: @SpringBootConfiguration:组合了 ...

  2. zookeeper 是什么?zookeeper 都有哪些功能?

    zookeeper 是什么? ZooKeeper由雅虎研究院开发,是Google Chubby的开源实现,后来托管到Apache,于2010年11月正式成为Apache的顶级项目.ZooKeeper是 ...

  3. Redis 常见的性能问题都有哪些?如何解决?

    Redis 常见的性能问题都有哪些?如何解决? Master写内存快照,save命令调度rdbSave函数,会阻塞主线程的工作,当快照比较大时对性能影响是非常大的,会间断性暂停服务,所以Master最 ...

  4. Oracle入门基础(三)一一单行函数

    SQL> --字符函数 SQL> select lower('Hello World') 转小写,upper('Hello World') 转大写,initcap('hello world ...

  5. We're sorry but demo3 doesn't work properly without JavaScript enabled. Please enable it to continue.

    今天遇到一个问题为 vue请求得到的响应为 We're sorry but demo3 doesn't work properly without JavaScript enabled. Please ...

  6. 用maven建立一个工程5

    在命令行里面输入cd myapp再按回车 再输入mvn compile再按回车 再输入 cd target按回车 再输入cd../按回车 再输入mvn package按回车 最后输入java -cla ...

  7. SpringCloud个人笔记-03-Config初体验

    sb-cloud-config 配置中心 <?xml version="1.0" encoding="UTF-8"?> <project xm ...

  8. isNotEmpty 与 isNotBlank 的区别

    isNotEmpty(str)等价于 str != null && str.length > 0 isNotBlank(str) 等价于 str != null &&am ...

  9. C++大作业——教职工管理系统

    教职工信息管理系统 1.问题描述: 设计一个学校职工管理系统,要求实现如下功能:建立职工信息数据, 包括职工编号.姓名. 性别.工资.出生时间.岗位.参加工作时间和年 龄(必须计算得到),初始模拟数据 ...

  10. PCB基础知识(一)

    在电子行业有一个关键的部件叫做PCB(printed circuit board,印刷电路板).这是一个太基础的部件,导致很多人都很难解释到底什么是PCB.这篇文章将会详细解释PCB的构成,以及在PC ...