我正在尝试尽快从RGBA图像数据中复制1或2个颜色通道(这是我代码中最慢的部分,并且它会减慢整个应用程序的速度)。是否有快速复制的方法?
数据简单地布局为RGBARGBARGBA等,我只需要复制R值,或者只需要复制RG值。
到目前为止我所拥有的大致是复制R值:
for(int i=0; i<dataSize; i++){
dest[i] = source[i*4];
}
对于RG值,我正在做:
for(int i=0; i<dataSize; i+=2){
dest[i] = source[i*2];
dest[i+1] = source[(i*2)+1];
}
所有数据都是无符号的1字节值。有更快的方法吗?我已经部分展开了循环(每次迭代执行64个值 - 超出该值的微不足道的加速)。平台是Armv7(iOS),所以使用NEON(SIMD)可能很有用,不幸的是我没有经验!
遗憾的是,更改数据是由opengl的readPixels()函数提供的,并且iOS不支持读取为L,LA,RG等,据我所知。
答案 0 :(得分:6)
如果你对iOS4及更高版本没问题,你可能会发现vDSP和加速框架很有用。 Check out the documentation用于在经线速度下的各种图像处理优度。
#import <Accelerate/Accelerate.h>
我不知道你接下来会做什么,但是如果你对图像数据做任何形式的计算,并希望它以浮点形式,你可以使用vDSP_vfltu8将源字节数据的一个通道转换为单精度浮点使用这样的单行(不包括内存管理);
vDSP_vfltu8(srcData+0,4,destinationAsFloatRed,1,numberOfPixels)
vDSP_vfltu8(srcData+1,4,destinationAsFloatGreen,1,numberOfPixels)
vDSP_vfltu8(srcData+2,4,destinationAsFloatBlue,1,numberOfPixels)
vDSP_vfltu8(srcData+3,4,destinationAsFloatAlpha,1,numberOfPixels)
如果您需要从操纵的浮点数据创建图像,请使用vDSP_vfuxu8返回其他方式 - 所以;
vDSP_vfixu8(destinationAsFloatRed,1,outputData+0,4,numberOfPixels);
vDSP_vfixu8(destinationAsFloatGreen,1,outputData+1,4,numberOfPixels);
vDSP_vfixu8(destinationAsFloatBlue,1,outputData+2,4,numberOfPixels);
vDSP_vfixu8(destinationAsFloatAlpha,1,outputData+3,4,numberOfPixels);
显然,您可以使用上述技术处理1个或2个频道。
文档非常复杂,但结果很好。
答案 1 :(得分:4)
因为总是加载和存储是最昂贵的操作。 您可以通过以下方式优化代码:
代码只是快速输入以获得想法。
unsigned int tmp;
unsigned int *dest;
for(int i=0; i<dataSize; i+=4){
tmp = (source[i] & 0xFF);
tmp |= (source[i+1] & 0xFF) << 8;
tmp |= (source[i+2] & 0xFF) << 16;
tmp |= (source[i+3] & 0xFF) << 24;
*dest++ = tmp;
}
答案 2 :(得分:3)
根据编译的代码,您可能希望将多重复制替换为2,并添加第二个循环索引(称之为j
并将其前进4):
for(int i=0, j=0; i<dataSize; i+=2, j+=4){
dest[$i] = source[$j];
dest[$i+1] = source[$j+1];
}
或者,您可以将乘法替换为1:
for(int i=0, j=0; i<dataSize; i+=2, j+=4){
dest[$i] = source[$i<<1];
dest[$i+1] = source[($i<<1)+1];
}
答案 3 :(得分:3)
我更像是while
家伙 - 您可以将其转换为for
,我确定
i = j = 0;
while (dataSize--) {
dst[i++] = src[j++]; /* R */
dst[i++] = src[j++]; /* G */
j += 2; /* ignore B and A */
}
至于它更快,你必须测量。
答案 4 :(得分:2)
罗杰的答案可能是最干净的解决方案。拥有一个库来保持代码的小巧总是好的。但是如果你只想优化C代码,你可以尝试不同的东西。首先,您应该分析dataSize的大小。然后,您可以执行重循环展开,可能与复制int而不是字节组合:(伪代码)
while(dataSize-i > n) { // n being 10 or whatever
*(int*)(src+i) = *(int*)(dest+i); i++; // or i+=4; depending what you copy
*(int*)(src+i) = *(int*)(dest+i);
... n times
}
然后用:
完成剩下的工作switch(dataSize-i) {
case n-1: *(src+i) = *(dest+i); i++;
case n-2: ...
case 1: ...
}
它有点难看......但它确实很快:)
如果你知道dataSize的行为方式,你可以进行更多优化。也许它总是2的力量?还是偶数?
我刚刚意识到你不能一次复制4个字节:)但只有2个字节。无论如何,我只是想告诉你如何结束一个只有1个比较的switch语句的展开循环。 IMO是获得体面加速的唯一途径。
答案 5 :(得分:2)
你的问题仍然存在吗?我已经发布了我的ASM加速函数,用于在几天前大步复制字节。它比相应的C代码快两倍。你可以在这里找到它:https://github.com/noveogroup/ios-aux它可以被修改为在RG字节复制的情况下复制单词。
UPD:我发现默认情况下关闭编译器的优化时,我的解决方案仅在调试模式下比C代码更快。在发布模式下,C代码已经过优化(默认情况下),并且与我的ASM代码一样快。
答案 6 :(得分:1)
您对ASM感到满意吗?我不熟悉ARM处理器,但在ADI公司的Blackfin上,这个副本实际上是免费的,因为它可以与计算操作并行完成:
i0 = _src_addr;
i1 = _dest_addr;
p0 = dataSize - 1;
r0 = [i0++];
loop _mycopy lc0 = p0;
loop_begin _mycopy;
/* possibly do compute work here | */ r0 = [i0++] | W [i1++] = r0.l;
loop_end _mycopy;
W [i1++] = r0.l;
因此,您有每像素1个周期。请注意,原样,这适用于RG或BA副本。正如我所说的,我对ARM并不熟悉,对iOS一无所知所以我不确定你是否可以访问ASM代码,但你可以尝试寻找那种优化。
答案 7 :(得分:1)
希望我参加晚会不会太晚!我刚刚使用ARM NEON内在函数在iPad上完成了类似的工作。与其他列出的答案相比,我的速度提高了2-3倍。请注意,下面的代码只保留第一个通道,并要求数据为32个字节的倍数。
uint32x4_t mask = vdupq_n_u32(0xFF);
for (unsigned int i=0, j=0; i < dataSize; i+=32, j+=8) {
// Load eight 4-byte integers from the source
uint32x4_t vec0 = vld1q_u32((const unsigned int *) &source[i]);
uint32x4_t vec1 = vld1q_u32((const unsigned int *) &source[i+16]);
// Zero everything but the first byte in each of the eight integers
vec0 = vandq_u32(vec0, mask);
vec1 = vandq_u32(vec1, mask);
// Throw away two bytes for each of the original integers
uint16x4_t vec0_s = vmovn_u32(vec0);
uint16x4_t vec1_s = vmovn_u32(vec1);
// Combine the remaining bytes into a single vector
uint16x8_t vec01_s = vcombine_u16(vec0_s, vec1_s);
// Throw away the last byte for each of the original integers
uint8x8_t vec_o = vmovn_u16(vec01_s);
// Store to destination
vst1_u8(&dest[j], vec_o);
}