在C中大步复制内存的最快方法?

时间:2011-06-27 08:09:11

标签: iphone c memory stride

我正在尝试尽快从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等,据我所知。

8 个答案:

答案 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)

因为总是加载和存储是最昂贵的操作。 您可以通过以下方式优化代码:

  • 加载一个int(RGBA)
  • 将所需部件存储在寄存器(临时变量)
  • 将数据移至temp变量中的正确位置。
  • 执行此操作,直到本机处理器数据大小已满(32位计算机上的字符数为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);
}