在C++中复制跨步数据
Copying strided data in C++
我有两个数组,我想以一定的速度将一个数组复制到另一个数组中。例如,我有
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++中有效地执行跨步内存复制?
谢谢。
编辑 - 问题澄清
为了使问题更清楚,让我们用 a
和 b
来表示手头的两个数组。我有一个执行唯一以下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 项复制到新阵列。
- 矢量如何将数据复制到另一个矢量?
- 将矢量的数据复制到<MyStruct>矢量<MyStruct>的指针
- 将父类的子类的数据复制到具有相同父类的另一个类
- 使用 memcpy 将矢量数据复制到 wstring 的正确方法
- C++:传递指向函数中类的指针会导致数据复制?
- 将数据复制到磁盘上新位置的语法
- pybind11:如何包装以 std::vector<double> 为参数以避免数据复制的 C++ 函数
- 如何将数据复制到字节数组的某些部分
- 使用Sapera拍摄图像并将数据复制到矢量
- 如果strncpy将随机数据复制到缓冲区中会发生什么
- C++ 促进序列化、构造函数和数据复制
- 将一个通道数据复制到OpenCV中的另一个通道
- SSE将数据复制到变量
- doxygen将doxygen注释中的特定数据复制到markdown页面中
- 正在将指针指向的数据复制到另一个指针中
- 使用 CUDA 将大数据复制到 GPU 和从 GPU 复制大数据
- 尝试将.csv数据复制到c++中的结构时出现堆栈溢出错误
- 数据复制与封装
- 如何将 frontBuffer 数据复制到纹理 DirectX 9
- 更快地将数组中的数据复制到目标,同时应用缩放或偏移因子