12

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

4

8 回答 8

5

如果您对 iOS4 及更高版本没问题,您可能会发现 vDSP 和加速框架很有用。查看文档以了解经线速度下的各种图像处理优势。

#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 个通道。

文档相当复杂,但结果很好。

于 2011-06-27T08:31:42.337 回答
3

与往常一样,加载和存储是最昂贵的操作。您可以通过以下方式优化您的代码:

  • 加载一个整数 (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;
}
于 2011-06-27T10:12:30.240 回答
2

j根据编译的代码,您可能希望通过添加第二个循环索引(调用它并将其推进 4)将乘法替换为 2 :

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];
}
于 2011-06-27T08:21:00.683 回答
2

我更像一个while男人——你可以把它转换成for,我敢肯定

i = j = 0;
while (dataSize--) {
    dst[i++] = src[j++]; /* R */
    dst[i++] = src[j++]; /* G */
    j += 2;              /* ignore B and A */
}

至于它更快,你必须测量。

于 2011-06-27T08:25:13.820 回答
1

罗杰的回答可能是最干净的解决方案。有一个库来保持你的代码很小总是好的。但是,如果您只想优化 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 是获得体面加速的唯一方法。

于 2011-06-27T08:50:37.147 回答
1

你的问题仍然是真实的吗?几天前,我发布了我的 ASM 加速函数,用于大步复制字节。它比相应的 C 代码快大约两倍。你可以在这里找到它:https ://github.com/noveogroup/ios-aux可以修改为复制单词以防 RG 字节复制。

UPD:我发现当编译器的优化默认关闭时,我的解决方案仅在调试模式下比 C 代码更快。在发布模式下,C 代码经过优化(默认情况下),运行速度与我的 ASM 代码一样快。

于 2013-09-05T10:33:58.600 回答
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);
}
于 2014-07-31T00:31:54.997 回答
0

您对 ASM 满意吗?我不熟悉 ARM 处理器,但在 Analog Devices 的 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 代码,但你可以尝试寻找这种优化。

于 2011-06-27T13:14:28.153 回答