2021SC@SDUSC
并行程序优化SLIC超像素算法
配置并行程序运行环境
简单起见直接采用openmp(g++/gcc自带)
gcc main.cpp -fopenmp
源代码(未优化)
源代码是未并行的程序
SLIC.h
SLIC.cpp
运行结果如下:
Computing time=26789 ms
There are 0 points' labels are different from original file.
后面是付费内容
intel编译器配置
-
下载完成之后,安装,一直continue下一步即可
安装目录默认在C:Program File(x86)/Intel/oneAPI
-
执行
"C:\Program Files (x86)\Intel\oneAPI\setvars.bat"
初始化环境变量对于powershell执行
cmd.exe "/K" '"C:\Program Files (x86)\Intel\oneAPI\setvars.bat" && powershell'
-
在项目目录下运行
oneapi-cli.exe
计时器(精确到微秒)
#include <ctime>
timeval t1 = timeval();
timeval t2 = timeval();
mingw_gettimeofday(&t1, nullptr);
// some code
mingw_gettimeofday(&t2, nullptr);
// ms显示
printf("\nfinish: time %d ms", (t2.tv_sec - t1.tv_sec) * 1000 + (t2.tv_usec - t1.tv_usec) / 1000);
openmp计时器
#include <omp.h>
double start_time, end_time;
start_time = omp_get_wtime();
//do some functions
end_time = omp_get_wtime();
printf("Spend time: %.3lf s\n", end_time - start_time);
整数与浮点运算速度
先上结论,整数(int,long)与浮点数(float,double)在±*方面速度几乎一致
在除法中浮点数除法明显慢于整数出发
浮点数的乘法会略快于整数乘法
debug
在数千万条数据里总有几个与众不同
于是优先从聚类中心中debug(数量少)
第一次迭代的时候聚类中心获取到的像素点数量全都一致
进一步查看聚类中心的变换情况,发现从第56个聚类中心开始数据有变动,因此聚集的像素点数量相同,但是实际像素不同,或者是像素的转化出现问题,结合之前直接导入代码没有问题,应该不是像素的转换出了问题,是实际的运算问题
回到计算聚类的平均质心之前的求和部分,发现加法的时候已经出现了问题,而且问题出现在的57号聚类中心恰好有两个nan的存在,应该是两个nan导致后面所有的求和都出现了问题
进一步发现问题出现在了labxy中的bx两个向量上
对初始化的数组进行输出没有nan !还真有nan确定问题是初始化时忘记五维向量的系数
解决这个问题之后发现结果还是不对
再走流程,发现聚类中心在第一次迭代是一致的
通过二分法,找到第一次不一致的地方,发现从第二次开始又不对了
原来是初始化有两个地方,忘了改另一个了
初始化一定要检查清楚,感觉没问题出了问题,一定要检查初始化语句是不是写对了
优化记录
面向对象与面向过程优化
原来的程序时基于面向对象的,面向对象就必然有类的开销,优化程序的过程中先优化面向对象
-
将所有类的变量提取出来
-
将所有的方法孤立出来,通过全局变量传参
这里使用全局变量主要是为了方便后期将所有的函数合一,减少函数调用时间
-
替换所有的vector为数组,在使用vector的时候,也是为了便利性牺牲了一定的性能,通过vector的替换可以省去许多函数调用,提高性能,利用数组与指针更加贴近于汇编操作,方便编译器进行优化操作
-
自己实现vector数组替换原始的面向对象的vector
优化赋值计算操作
-
原始的所有赋值操作是通过for循环进行赋值
-
用到系统自带的memset与memcpy会显著的提升赋值的性能
通过测试1^9次赋值操作大约需要1s
-
优化浮点除法,浮点除的性能明显低于浮点±*
-
将浮点除法转换为浮点乘法,在编译期间就计算出可以计算的除法的结果
-
利用cebt立方根函数,代替pow函数
使用缓存替代计算
- 在颜色转换中对rgb转换成lab需要进行多次浮点运算以及多次立方运算,性能很低
- 首先颜色空间的转换需要先从rgb转换为xyz,这一过程需要的是一次pow(x,2.4)的运算,考虑对于固定的rgb每次的起始运算都一致,只需要提前计算256种颜色的pow运算就可以省区后续的pow,只需要查表就可以了,大约优化0.2s
- 有多个颜色可能会同时出现在界面上,这个时候可以利用hashmap一类的函数,对颜色转换进行缓存,也可以省去许多浮点运算以及pow运算(在多线程的条件下,缓存命中率会明显下降,效率反而降低)
优化cache命中率
- 对于原始的cache,由于rgb与lab都是分开存储的cache的命中率不一定很高
- 使用三维数组直接存放rgb与lab相邻的lab与rgb基本上一定会命中cpu cache会提高效率 大约优化0.4-0.6s
- 对线程进行较大块的分割减少线程消耗同时,增加部分命中率
- 进行字节对齐提高内存读取速度
优化多次相同操作 smid
对于相似的操作,只是单纯的利用多核效果并不出色,可以考虑到,由于cpu拥有多个运算单元,往往利用向量化,可以同时进行多个运算单元并行操作进一步提高并行化效果
原始代码如下所示
seeds[kn + 0] = sigma[kn + 0] * inv;
seeds[kn + 1] = sigma[kn + 1] * inv;
seeds[kn + 2] = sigma[kn + 2] * inv;
seeds[kn + 3] = sigma[kn + 3] * inv;
seeds[kn + 4] = sigma[kn + 4] * inv;
这五个明显是几乎相同的代码,因此可以进行向量化操作
#pragma omp simd
for (int i = 0; i < 5; ++i) {
seeds[kn + i] = sigma[kn + i] * inv;
}
多线程并行
利用openmp可以实现多线程并行,下面一行就是for循环的迭代过程
#pragma omp parallel for default(none) schedule(guided) shared()
对于schedule,经过系统测试,设置为guid,且把最小值设置为8的性能最高
信仰优化
使用编译器O3优化 (O3不如O2快,不知是不是个例)
这部分优化时间不确定,在初始代码中大约可以从35s优化到30s
经过重写改进的代码大约可以从10s优化到4s
经过并行改进的代码大约可以从4s优化到2s