在C++中复制跨步数据

Copying strided data in C++

本文关键字:数据 复制 C++      更新时间:2023-10-16

我有两个数组,我想以一定的速度将一个数组复制到另一个数组中。例如,我有

A A A A A A A A ...
B B B B B B B B ...

我想复制B的每三个元素来A以获得

B A A B A A B A ...

从帖子"有标准的,跨步的memcpy版本吗?",似乎在C中没有这种可能性。

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

我的问题是;有没有办法在至少作为标准for循环执行C++中有效地执行跨步内存复制

谢谢。

编辑 - 问题澄清

为了使问题更清楚,让我们用 ab 来表示手头的两个数组。我有一个执行唯一以下for循环的函数

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

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

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

可能是一个过于具体的答案,但在支持 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 级:常规 + 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 毫秒。作为参考,640*480 字节的memcpy,或此处测试量的两倍,平均需要大约 0.6 毫秒。

作为旁注,使用上述 NEON 参数编译的第二个代码(展开和指针(需要大约相同的时间,0.6 毫秒。

有没有办法在至少作为标准 for 循环执行C++中有效地执行跨步内存复制?

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

由于跨步复制

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

假设使用标准for循环,则可以通过使用循环展开来获得一些性能。 一些编译器具有展开循环的选项;这不是"标准"选项。

给定一个标准的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 项复制到新阵列。