将不对齐结构体的数组中的数据移动到对齐的数组中(C++)。

7
什么是从CameraSpacePoint数组到PointXYZ数组移动数据的最佳方式?
struct CameraSpacePoint
{
    float X;
    float Y;
    float Z;
};

 __declspec(align(16))
 struct PointXYZ
 {
      float x;
      float y;
      float z;
 };

 constexpr int BIG_VAL = 1920 * 1080;

 CameraSpacePoint  camera_space_points[BIG_VAL];
 PointXYZ          points_xyz[BIG_VAL];

我的解决方案:

CameraSpacePoint* camera_space_points_ptr = &camera_space_points[0];
PointXYZ*         points_xyz_ptr          = &points_xyz[0];

for (int i = 0; i < BIG_VAL; ++i)
{
    memcpy(points_xyz_ptr++, camera_space_points_ptr++, sizeof(CameraSpacePoint));
}

这是最有效的方法吗?


2
只需使用 std::copy... - You
1
@你:std::copy需要从当前未提供的CameraSpacePoint转换为PointXYZ... - Jarod42
1
如果这些点是为SIMD目的对齐的,那么这仍然是一种不太理想的布局。 - harold
@harold:我正在创建一个适配器,用于连接两个API(Kinect SDK 2.0和PCL),并在这种情况下进行了限制。 - Dmitry Zhivaev
好的,不知道我当时在想什么... - You
显示剩余17条评论
2个回答

3

像往常一样,易读性和可维护性优先于其他问题。写出你的意思,不要修复没有问题的内容:在优化之前进行测量。

std::transform(camera_space_points, std::end(camera_space_points), points_xyz,
    [](auto c){
        return PointXYZ{c.X, c.Y, c.Z};
    });

这是默认情况下应该始终编写的内容。通过它们的 汇编输出快速基准测试,这与 memcpy 版本几乎相同。
更泛泛地说,优化器非常擅长微调简单代码,例如复制大块内存,手动优化很少更好。

太棒了!你的解决方案比我的快了66%!20毫秒对12毫秒!非常感谢 :) - Dmitry Zhivaev
@路人甲:我赶时间。如果我只复制一次,那么结果是20毫秒比12毫秒更快。如果我连续复制10次,那么结果是57毫秒比115毫秒更快。如果我连续复制100次,那么结果是400毫秒比1140毫秒更快。在我的情况下,memcpy更快更适合。 - Dmitry Zhivaev
1
我认为这是高度主观的,哪个版本更易读。对我来说,OP的版本比你提出的解决方案更易读(我并不是说你的版本不好/不易读,只是OP的版本对我来说更易读)。所以我不同意“这就是你应该始终编写的默认内容”。此外,OP寻求“最有效”的方法。在这种情况下,可读性/可维护性并不是最重要的事情。当然,如果它可以被读取/维护,那么我们应该去做,但如果可读性/可维护性影响性能,那么我们不应该使用这样的解决方案。 - geza
@geza 可读性可能是主观的,但在这种特定情况下,我认为它是客观上更易读和可维护的。除了命令式与声明式之外,这个版本使你不必考虑琐碎的可复制类型、偏移量错误和不对齐的读取,所有这些都极易出错。关于性能问题,由于我们没有确切的使用情况,因此我很难在任何一方面进行争论。 - Passer By
@0kcats 不安全...怎么了?边界检查是你的责任,这与sprintf不是类似的。 - Passer By
显示剩余3条评论

2
一种替代方法是确保您复制16字节的块。这样,如果存在一些一次复制16个字节的指令(在x64上存在,所以在Xbox One和PC等设备上可以提高效率),则可以更好地优化复制操作(而不需要任何周围的洗牌或其他不必要的复杂性)。PointXYZ将占用16个字节,因此将16个字节写入它们是可以接受的。源具有12个字节的元素,因此每次以这种方式复制其中一个元素时,也会复制4个字节来自它后面的元素,在目标PointXYZ的填充区域中被忽略。最后一个CameraSpacePoint之后可能没有4个可读字节,它可能正好位于一个未映射/不可读取的内存区域之前,因此我们需要注意不要进一步读取 - 除非可以稍微扩展该数组以保证内存的存在。

例如:

auto dst = ::dst;
auto src = ::src;
for (int i = 0; i + 1 < BIG_VAL; ++i)
    std::memcpy(dst++, src++, 16);
// last point is special, since the src may not have 16 bytes left to read
std::memcpy(dst, src, sizeof(CameraSpacePoint));

(在godbolt网站上)


展开循环不会有一点帮助吗?我看到clang已经展开了它,但MSVC没有展开,OP可能在使用MSVC。 - geza
@geza 这应该有所帮助,因为 MSVC 在那里有点傻。 - harold
顺便问一下,在这种情况下确切的实现是否重要?输入数据约为24MB,整个流程很可能受到内存带宽的限制,是吗? - geza

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接