我正在尝试写一个安卓应用程序,需要计算多个全分辨率图像的高斯和拉普拉斯金字塔,我写了这与NDK在C++上,代码的最关键的部分是应用高斯滤波器的图像,我正在应用这个过滤器与水平和垂直。
过滤器是(0.0625,0.25,0.375,0.25,0.0625),因为我正在计算(1,4,6,4,1)/16
dst[index] = ( src[index-2] + src[index-1]*4 + src[index]*6+src[index+1]*4+src[index+2])/16;我已经做了一些简单的优化,但是它的运行速度仍然比预期的慢,我想知道是否有任何其他的优化选项我错过了。
PS:我应该提到的是,我已经尝试写这个过滤器部分与内联臂装配,但它给出2倍慢的结果。
//horizontal filter
for(unsigned y = 0; y < height; y++) {
for(unsigned x = 2; x < width-2; x++) {
int index = y*width+x;
dst[index].r = (src[index-2].r+ src[index+2].r + (src[index-1].r + src[index+1].r)*4 + src[index].r*6)>>4;
dst[index].g = (src[index-2].g+ src[index+2].g + (src[index-1].g + src[index+1].g)*4 + src[index].g*6)>>4;
dst[index].b = (src[index-2].b+ src[index+2].b + (src[index-1].b + src[index+1].b)*4 + src[index].b*6)>>4;
}
}
//vertical filter
for(unsigned y = 2; y < height-2; y++) {
for(unsigned x = 0; x < width; x++) {
int index = y*width+x;
dst[index].r = (src[index-2*width].r + src[index+2*width].r + (src[index-width].r + src[index+width].r)*4 + src[index].r*6)>>4;
dst[index].g = (src[index-2*width].g + src[index+2*width].g + (src[index-width].g + src[index+width].g)*4 + src[index].g*6)>>4;
dst[index].b = (src[index-2*width].b + src[index+2*width].b + (src[index-width].b + src[index+width].b)*4 + src[index].b*6)>>4;
}
}发布于 2012-10-25 06:38:23
因为只有当y改变时才会发生乘法运算,所以可以将index乘法从内部循环中分解出来:
for (unsigned y ...
{
int index = y * width;
for (unsigned int x... 您可以通过在使用变量之前加载变量来提高速度。这将使处理器将它们加载到缓存中:
for (unsigned x = ...
{
register YOUR_DATA_TYPE a, b, c, d, e;
a = src[index - 2].r;
b = src[index - 1].r;
c = src[index + 0].r; // The " + 0" is to show a pattern.
d = src[index + 1].r;
e = src[index + 2].r;
dest[index].r = (a + e + (b + d) * 4 + c * 6) >> 4;
// ... 另一个技巧是“缓存”src的值,这样每次只添加一个新的值,因为src[index+2]中的值最多可以使用5次。
下面是这些概念的一个例子:
//horizontal filter
for(unsigned y = 0; y < height; y++)
{
int index = y*width + 2;
register YOUR_DATA_TYPE a, b, c, d, e;
a = src[index - 2].r;
b = src[index - 1].r;
c = src[index + 0].r; // The " + 0" is to show a pattern.
d = src[index + 1].r;
e = src[index + 2].r;
for(unsigned x = 2; x < width-2; x++)
{
dest[index - 2 + x].r = (a + e + (b + d) * 4 + c * 6) >> 4;
a = b;
b = c;
c = d;
d = e;
e = src[index + x].r;发布于 2012-10-25 06:31:22
我不确定你的编译器是如何优化这一切的,但我倾向于使用指针。假设你的结构是3个字节...您可以将指针放在正确的位置(源的过滤器边缘和目标的目标),然后使用常量数组偏移量移动它们。我还在外部循环中添加了一个可选的OpenMP指令,因为这也可以改善情况。
#pragma omp parallel for
for(unsigned y = 0; y < height; y++) {
const int rowindex = y * width;
char * dpos = (char*)&dest[rowindex+2];
char * spos = (char*)&src[rowindex];
const char *end = (char*)&src[rowindex+width-2];
for( ; spos != end; spos++, dpos++) {
*dpos = (spos[0] + spos[4] + ((spos[1] + src[3])<<2) + spos[2]*6) >> 4;
}
}垂直循环也是如此。
const int scanwidth = width * 3;
const int row1 = scanwidth;
const int row2 = row1+scanwidth;
const int row3 = row2+scanwidth;
const int row4 = row3+scanwidth;
#pragma omp parallel for
for(unsigned y = 2; y < height-2; y++) {
const int rowindex = y * width;
char * dpos = (char*)&dest[rowindex];
char * spos = (char*)&src[rowindex-row2];
const char *end = spos + scanwidth;
for( ; spos != end; spos++, dpos++) {
*dpos = (spos[0] + spos[row4] + ((spos[row1] + src[row3])<<2) + spos[row2]*6) >> 4;
}
}不管怎样,这就是我做卷积的方式。它牺牲了一点可读性,而我从来没有尝试过衡量这种差异。我只是倾向于从一开始就这样写。看看这能不能让你加速。如果你有一台多核机器,OpenMP肯定会,而指针之类的东西可能会。
我喜欢关于使用SSE进行这些操作的评论。
发布于 2012-10-25 19:17:25
一些更明显的优化利用了内核的对称性:
a=*src++; b=*src++; c=*src++; d=*src++; e=*src++; // init
LOOP (n/5) times:
z=(a+e)+(b+d)<<2+c*6; *dst++=z>>4; // then reuse the local variables
a=*src++;
z=(b+a)+(c+e)<<2+d*6; *dst++=z>>4; // registers have been read only once...
b=*src++;
z=(c+b)+(d+a)<<2+e*6; *dst++=z>>4;
e=*src++;第二件事是可以使用单个整数执行多个加法。当要过滤的值是无符号的时,可以在单个32位整数中容纳两个通道(或在64位整数中容纳4个通道);这是穷人的SIMD。
a= 0x[0011][0034] <-- split to two
b= 0x[0031][008a]
----------------------
sum 0042 00b0
>>4 0004 200b0 <-- mask off
mask 00ff 00ff
-------------------
0004 000b <-- result (模拟的SIMD显示一个加法,然后移位4)
这是一个并行计算3个rgb操作的内核(在64位体系结构中很容易修改为6个rgb操作...)
#define MASK (255+(255<<10)+(255<<20))
#define KERNEL(a,b,c,d,e) { \
a=((a+e+(c<<1))>>2) & MASK; a=(a+b+c+d)>>2 & MASK; *DATA++ = a; a=DATA[4]; }
void calc_5_rgbs(unsigned int *DATA)
{
register unsigned int a = DATA[0], b=DATA[1], c=DATA[2], d=DATA[3], e=DATA[4];
KERNEL(a,b,c,d,e);
KERNEL(b,c,d,e,a);
KERNEL(c,d,e,a,b);
KERNEL(d,e,a,b,c);
KERNEL(e,a,b,c,d);
}在ARM和具有16个寄存器的64位IA上工作得最好...需要大量的汇编器优化,以克服32位IA中的寄存器不足(例如,使用ebp作为GPR)。正因为如此,它是一种原地算法。
每8位数据之间只有2个守护位,这足以得到与整数计算完全相同的结果。
顺便说一句:只按字节遍历数组字节比按r,g,b元素遍历要快
unsigned char *s=(unsigned char *) source_array;
unsigned char *d=(unsigned char *) dest_array;
for (j=0;j<3*N;j++) d[j]=(s[j]+s[j+16]+s[j+8]*6+s[j+4]*4+s[j+12]*4)>>4;https://stackoverflow.com/questions/13058315
复制相似问题