前言
本实验是基于 CS:APP 书中提供的 ICS+ 课程内容而成的,这门课程旨在培养能够理解硬件、操作系统、编译系统和系统编程对应用程序性能、正确性影响的程序员,不涉及低层次的处理器体系结构,也就是本实验不会涉及 CS:APP 第四章的内容。因此实验四直接指向第五章「优化程序性能」。
介绍
通常来说,需要优化程序性能的算法主要存在于「基础设施类」的代码,如标准函数库。因此,我们将本实验聚焦于 OpenCV 的算法。本实验包含 1 道有关数字图像处理的算法优化题目,即将数字图像顺时针旋转 90°。
数字图像,本质上是离散像素及像素属性的特定排列,简单来说就是「非常多不同颜色的方格」。因此将数字图像顺时针旋转 90°,只需要将每个「方格」逐个转移到正确的特定位置即可。
在本实验中,单个数字图像被定义为二维矩阵,其中每个单位元素为只带有 RGB 信息的像素。在实验指导书中,我将给出顺时针旋转 90° 的数学表达式,使学生在没有数字图像处理相关背景知识的情况下也可以完成实验。
具体内容
学生需要优化下面的函数,并使用 GPROF 工具来检查自己编写函数的性能。实验五的测试程序和测试用例集已经在文件中一并给出,学生只需简单地运行编译好的程序即可检查实验结果。
void clockwiseRotate(int dimension, pixel* src, pixel* dst) {
for(int i = 0; i < dimension; i++) {
for(int j = 0; j < dimension; j++) {
dst[locate(j, dimension-1-i, dimension)] = src[locate(i, j, dimension)];
}
}
}
这个函数的作用是将分辨率为 dimension * dimension
的数字图像 src
顺时针旋转旋转 90° 并保存到 dst
中。函数原型及实现已经完成,学生要做的是优化这个程序使其运行速度更快。
文件内容
- main.cc:clockwiseRotate() 函数所在位置,学生需要修改的文件
- test.cc:测试驱动文件
- driver.h:实验四的头文件
- Makefile:简化编译过程的文件
- README:实验指导书实验四部分节选
实验过程简介
如上文所介绍的,学生需要修改位于 main.cc 文件中的函数 clockwiseRotate(),并通过 Makefile 进行编译,然后通过 GPROF 工具检查程序的运行时间是否已经优化成功。优化的方式在 CS:APP 第五章中有大量的介绍,每个方式都可以或多或少地提高程序的运行速度,这里不展开解释:
- 代码移动
- 减少过程调用
- 消除不必要的内存引用
- 创建多个累积变量
- 循环展开
- 重新结合变换
在优化完函数后,使用 Make 工具进行编译,可以得到一个可执行文件 rotate。通过运行这个可执行文件,学生可以检查自己的函数是否「正确」。
unix> make
g++ -Wall -Og -pg main.cc test.cc -o rotate
unix> ./rotate
共测试 8 个图像尺寸数据,没有发现错误,
请使用 GPROF 进行性能测试。
运行后会生成一个 gmon.out 文件,使用 GPROF 工具可以生成一个包含性能分析信息的文本文件,通过该文件即可检查程序性能。
unix> gprof -b rotate gmon.out >> report
unix> vim report
文件的具体内容及其含义会在实验指导书中给出,这里不做介绍。
实验编写简介
- driver.h 文件
这个头文件中声明了所有需要用到的测试函数,以及下面两个重要的结构:
#define locate(i,j,n) ((i)*(n)+(j))
typedef struct {
unsigned short red;
unsigned short green;
unsigned short blue;
} pixel;
因为本实验的二维矩阵是通过一维数组实现的(后文介绍),因此需要一个定位函数将二维坐标映射到一维坐标,于是便有了上述第一个结构。这个结构中的 i、j 指二维坐标的行和列,而 n 指当前数字图像的尺寸。
第二个结构是像素,每个像素由 3 个无符号短整型组成 24 位 RGB 三原色构成,是数字图像的基本单位。
- test.cc
constexpr int MAX_DIMENSION = 8192;
pixel data[MAX_DIMENSION * MAX_DIMENSION * 3];
pixel *origin = NULL;
pixel *copyOfOrigin = NULL;
pixel *result = NULL;
int testCases[] = {27, 256, 81, 1024, 243, 4096, 729, 8192};
test.cc 最上方的全局变量,包括一个 MAX_DIMENSION 图像最大尺寸、由 MAX_DIMENSION 生成的一维数组 data、3 个指向特定存储位置的指针和 8 个测试用例。
data 的大小为 MAX_DIMENSION * MAX_DIMENSION * 3
,包含原始图像、原始图像的副本和旋转后的图像。原始图像指针指向数组相对地址 0,原始图像副本指针指向数组相对地址 MAX_DIMENSION * MAX_DIMENSION,而旋转后的图像指针指向数组相对地址 2 * MAX_DIMENSION * MAX_DIMENSION。
int main() {
int size = sizeof(testCases) / sizeof(testCases[0]);
int totalErrors = 0;
for(int i = 0; i < size; i++) {
int currentTestCase = testCases[i];
createImage(currentTestCase);
clockwiseRotate(currentTestCase, origin, result);
totalErrors += testRotate(currentTestCase);
}
......
}
void createImage(int dimension) {
origin = data;
copyOfOrigin = origin + dimension * dimension;
result = copyOfOrigin + dimension * dimension;
for(int i = 0; i < dimension; i++) {
for(int j = 0; j < dimension; j++) {
origin[locate(i,j,dimension)].red = randomValueOfPixel(0, 65536);
origin[locate(i,j,dimension)].green = randomValueOfPixel(0, 65536);
origin[locate(i,j,dimension)].blue = randomValueOfPixel(0, 65536);
copyOfOrigin[locate(i,j,dimension)].red = randomValueOfPixel(0, 65536);
copyOfOrigin[locate(i,j,dimension)].green = randomValueOfPixel(0, 65536);
copyOfOrigin[locate(i,j,dimension)].blue = randomValueOfPixel(0, 65536);
result[locate(i,j,dimension)].red = randomValueOfPixel(0, 65536);
result[locate(i,j,dimension)].green = randomValueOfPixel(0, 65536);
result[locate(i,j,dimension)].blue = randomValueOfPixel(0, 65536);
}
}
}
main 函数中,针对每一个测试点,首先生成一个随机图像,这个生成方式由上述的 createImage() 生成,针对每个像素给出一个随机值,并初始化指针;接着运行学生提供的旋转函数,最后通过测试函数 testRotate() 检查旋转函数是否正确。
int testRotate(int dimension) {
if(!checkOriginImg(dimension)) {
std::cout << "原始图像数据被修改过!" << std::endl;
return 0;
}
int error = 0;
int badi = 0, badj = 0;
pixel badOrigin, badResult;
for(int i = 0; i < dimension; i++) {
for(int j = 0; j < dimension; j++) {
if(!comparePixel(origin[locate(i,j,dimension)], result[locate(j,dimension-1-i,dimension)])) {
error++;
badi = i;
badj = j;
badOrigin = origin[locate(i,j,dimension)];
badResult = result[locate(j,dimension-1-i,dimension)];
}
}
}
}
而 testRotate() 函数中,则首先检查原始图像是否被修改过,接着逐像素检查学生提供的函数是否正确进行顺时针旋转 90° 操作,若存在错误则给出错误提示。
- Makefile
CC = g++
CPPFLAGS = -Wall -Og -pg
lock: main.cc test.cc driver.h
$(CC) $(CPPFLAGS) main.cc test.cc -o rotate
clean:
rm -f *.out rotate report
- 本实验由 C++ 写成,故使用 g++ 编译器进行编译
- 因为本实验最终是为了调试程序,故使用 -Og 参数表示在 -O1 的基础上,去掉影响调试的优化
- 参数 -pg 会生成可供 GPROF 剖析用的可执行文件