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. String 是最基本的数据类型吗?

    不是. Java中基本数据类型只有8个:byte.short.int.long.float.double.char.boolean:除了基本类型(primitive type),剩下都是引用类型(re ...

  2. jQuery--文档处理案例

    需求 如上图,实现左右两边的选择菜单可以左右移动,'>'按钮一次只能移动被选中的一个菜单,'>>'按钮一次移动所有被选择的菜单,'>>>'按钮 将所有菜单进行移动, ...

  3. (转载)一篇文章带你分清楚JWT,JWS与JWE

    是JWS(JSON Web Signature),也往往导致了人们对于JWT的误解,但是JWT并不等于JWS,JWS只是JWT的一种实现,除了JWS外,JWE(JSON Web Encryption) ...

  4. 机器学习之linear_model (线性回归算法模型)

    1.matplotlib 首先看一下这个静态图绘制模块 静态图形处理 数据分析三剑客 Numpy : 主要为了给pandas提供数据源 pandas : 更重要的数据结构 matplotlib : 静 ...

  5. Netty学习摘记 —— UDP广播事件

    本文参考 本篇文章是对<Netty In Action>一书第十三章"使用UDP广播事件"的学习摘记,主要内容为广播应用程序的开发 消息POJO 我们将日志信息封装成名 ...

  6. Spring官宣网传大漏洞,并提供解决方案

    Spring沦陷了!这样的标题这几天是不是看腻了?然而,仔细看看都是拿着之前的几个毫不相干的CVE来大吹特吹.所以,昨天发了一篇关于最近网传的Spring大漏洞的文章,聊了聊这些让人迷惑的营销文.以及 ...

  7. git和github学习笔记

    1. 了解Git和Github 2. 使用Github 3. Git安装和使用 4. Git基本工作流程 5. Git初始化及仓库创建和操作 6. Git管理远程仓库 7. Github Pages ...

  8. 单总线协议DS1820

    一. DS18B20简介 DS18B20数字温度传感器接线方便,封装后可应用于多种场合,如管道式,螺纹式,磁铁吸附式,不锈钢封装式.主要根据应用场合的不同而改变其外观.封装后的DS18B20可用于电缆 ...

  9. C++ | 智能指针初探

    智能指针初探 在 c/c++ 语言中有一种特殊的类型--指针类型. 指针作为实体,是一个用来保存一个内存地址的计算机语言中的变量.它可以直接对内存地址中的数据进行操作,是一种非常灵活的变量.指针被誉为 ...

  10. 8_LQR 控制器_状态空间系统Matlab/Simulink建模分析

    再线性控制器中讲到: 举例说明(线性控制器中的一个例子)博客中有说明 在matlab中:使用lqr求解K1.K2 这里希望角度(即x1)能迅速变化,所以Q矩阵中Q11为100,并没有关心角速度(dot ...