【CSAPP】Performance Lab 实验笔记
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操作,我主要优化了以下几点:
-
因为高速缓存读操作不命中的惩罚比写操作高,又因为空间局部性原则,所以优先在dst数组上以1为步长遍历。
-
为了消除冗余的运算,我们可以对
RIDX宏进行拆解,分析可知
dst[dim*dim-dim + i - dim*j] == src[dim*i + j]
- 根据讲义里提示所有图片尺寸为32的倍数,又因为CACHE_BLOCK大小为32,所以我们可对原代码进行32路展开
- 同样是根据空间局部性原则,尽量使内部循环步长短于外部循环
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 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操作,我的想法很直白:
- avg中有大量的冗余的max和min函数调用,可通过分类讨论四角、四边、中间的边界条件来优化之。
- 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
分数在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
还可以继续利用动态规划思想进行优化,但我懒得搞了。像这种分类情况多,代码量大的题目我确实是不怎么喜欢做。