13

我有两个数组,我想将一个数组复制到另一个数组中。例如,我有

A A A A A A A A ...

B B B B B B B B ...

我想复制 to 的每三个元素BA获得

B A A B A A B A ...

从帖子“ Is there a standard, strided version of memcpy? ”看来,C语言中似乎没有这种可能性。

但是,我经历过,在某些情况下,memcpy它比for循环的副本要快。

我的问题是;有什么方法可以有效地在 C++ 中执行跨步内存复制,至少作为标准for循环执行?

非常感谢。

编辑 - 问题的澄清

为了使问题更清楚,让我们用a和表示手头的两个数组b。我有一个执行唯一跟随for循环的函数

for (int i=0; i<NumElements, i++)
    a_[i] = b_[i];

其中两个[]' 都是重载的运算符(我正在使用表达式模板技术),因此它们实际上可以是平均的,例如

 a[3*i]=b[i];
4

2 回答 2

12

可能是一个过于具体的答案,但在支持 NEON 的 ARM 平台上,NEON 矢量化可用于使跨步复制更快。在资源相对有限的环境中,这可能会挽救生命,这可能是首先在该环境中使用 ARM 的原因。一个突出的例子是 Android,其中大多数设备仍然使用支持 NEON 的 ARM v7a 架构。

以下示例演示了这一点,将 YUV420sp 图像的半平面 UV 平面复制到 YUV420p 图像的平面 UV 平面是一个循环。源缓冲区和目标缓冲区的大小都是640*480/2字节。所有示例均使用 Android NDK r9d 中的 g++ 4.8 编译。它们在三星 Exynos Octa 5420 处理器上执行:

级别 1:常规

void convertUVsp2UVp(
    unsigned char* __restrict srcptr, 
    unsigned char* __restrict dstptr, 
    int stride)
{
    for(int i=0;i<stride;i++){
        dstptr[i]           = srcptr[i*2];
        dstptr[i + stride]  = srcptr[i*2 + 1];
    }
}

仅编译,-O3平均大约需要 1.5 毫秒。

第 2 级:使用移动指针展开和挤压更多

void convertUVsp2UVp(
    unsigned char* __restrict srcptr, 
    unsigned char* __restrict dstptr, 
    int stride)
{
    unsigned char* endptr = dstptr + stride;
    while(dstptr<endptr){
        *(dstptr + 0)             = *(srcptr + 0);
        *(dstptr + stride + 0)    = *(srcptr + 1);
        *(dstptr + 1)             = *(srcptr + 2);
        *(dstptr + stride + 1)    = *(srcptr + 3);
        *(dstptr + 2)             = *(srcptr + 4);
        *(dstptr + stride + 2)    = *(srcptr + 5);
        *(dstptr + 3)             = *(srcptr + 6);
        *(dstptr + stride + 3)    = *(srcptr + 7);
        *(dstptr + 4)             = *(srcptr + 8);
        *(dstptr + stride + 4)    = *(srcptr + 9);
        *(dstptr + 5)             = *(srcptr + 10);
        *(dstptr + stride + 5)    = *(srcptr + 11);
        *(dstptr + 6)             = *(srcptr + 12);
        *(dstptr + stride + 6)    = *(srcptr + 13);
        *(dstptr + 7)             = *(srcptr + 14);
        *(dstptr + stride + 7)    = *(srcptr + 15);
        srcptr+=16;
        dstptr+=8;
    } 
}

仅编译,-O3平均需要大约 1.15 毫秒。根据另一个答案,这可能与常规架构上的速度一样快。

第 3 级: Regular + GCC 自动 NEON 矢量化

void convertUVsp2UVp(
    unsigned char* __restrict srcptr, 
    unsigned char* __restrict dstptr, 
    int stride)
{
    for(int i=0;i<stride;i++){
        dstptr[i]           = srcptr[i*2];
        dstptr[i + stride]  = srcptr[i*2 + 1];
    }
}

用 编译-O3 -mfpu=neon -ftree-vectorize -ftree-vectorizer-verbose=1 -mfloat-abi=softfp,平均大约需要 0.6 毫秒。作为参考,一个字节,或者这里测试memcpy640*480两倍,平均需要大约 0.6 毫秒。

附带说明一下,使用上面的 NEON 参数编译的第二个代码(展开和指针)需要大约相同的时间,0.6 毫秒。

于 2014-10-07T07:56:34.810 回答
8

有什么方法可以有效地在 C++ 中执行跨步内存复制,至少作为循环的标准执行?

编辑 2: C++ 库中没有跨步复制的功能。

由于跨步复制不像内存复制那样流行,因此芯片制造商和语言设计都专门支持跨步复制。

假设一个标准for循环,您可以通过使用Loop Unrolling获得一些性能。一些编译器有展开循环的选项;这不是“标准”选项。

给定一个标准 for循环:

#define RESULT_SIZE 72
#define SIZE_A 48
#define SIZE_B 24

unsigned int A[SIZE_A];
unsigned int B[SIZE_B];
unsigned int result[RESULT_SIZE];

unsigned int index_a = 0;
unsigned int index_b = 0;
unsigned int index_result = 0;
for (index_result = 0; index_result < RESULT_SIZE;)
{
   result[index_result++] = B[index_b++];
   result[index_result++] = A[index_a++];
   result[index_result++] = A[index_a++]; 
}

循环展开将重复“标准”for循环的内容:

for (index_result = 0; index_result < RESULT_SIZE;)
{
   result[index_result++] = B[index_b++];
   result[index_result++] = A[index_a++];
   result[index_result++] = A[index_a++]; 

   result[index_result++] = B[index_b++];
   result[index_result++] = A[index_a++];
   result[index_result++] = A[index_a++]; 
}

展开的版本中,循环次数减少了一半。

与其他选项相比,性能改进可以忽略不计。以下问题会影响性能,并且每个问题都可能具有不同的速度改进:

  • 处理数据缓存未命中
  • 重新加载指令流水线(取决于处理器)
  • 操作系统用磁盘交换内存
  • 其他同时运行的任务
  • 并行处理(取决于处理器/平台)

并行处理的一个示例是让一个处理器将 B 项复制到新数组中,而另一个处理器将 A 项复制到新数组中。

于 2013-06-13T20:45:11.900 回答