【问题标题】:Move data from array of not aligned structs to array of aligned in c++将数据从未对齐的结构数组移动到 C++ 中的对齐数组
【发布时间】:2018-08-15 21:16:55
【问题描述】:

将数据从 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));
}

这是最有效的方法吗?

【问题讨论】:

  • 只需使用std::copy...
  • @You: std::copy 需要从 CameraSpacePoint 转换为 PointXYZ 目前未提供...
  • @You 它导致类型不匹配
  • 如果这些点为了 SIMD 目的而对齐,这仍然是一个有点不幸的布局
  • @harold:我正在两个 API(Kinect SDK 2.0 和 PCL)之间创建一个适配器,并在这种情况下进行钳制

标签: c++ memory optimization


【解决方案1】:

与往常一样,可读性和可维护性胜过其他问题。写下你的意思,不要修复不是问题的东西:在优化之前测量。

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

这是您应该始终默认编写的内容。通过他们的assembly outputquick benchmark,这几乎等同于memcpy 版本。

在一个更随意的说明中,优化器非常擅长对简单的代码进行微优化,例如复制一大块内存,手动优化几乎没有更好的。

【讨论】:

  • 对我来说看起来不太好,每个结构有两个加载和两个存储,而每个结构可以完成一个
  • 太棒了!你的解决方案比我的快 66%! 20 毫秒与 12 毫秒!非常感谢:)
  • @Passer By:我赶时间。如果我进行一次复制,则结果高于(20 ms 对 12 ms)。如果我连续进行 10 次复制,它将是 57 毫秒对 115 毫秒。如果我连续复制 100 次,它将是 400 毫秒与 1140 毫秒。就我而言,memcpy 更快,更适合。
  • 我认为这是非常主观的,哪个版本更具可读性。对我来说,OP 的版本比您提供的解决方案更具可读性(我并不是说您的版本不好/不可读,只是 OP 的版本对我来说更具可读性)。所以我不同意“这是您应该始终默认编写的内容”。另外,OP 寻求“最有效”的方式。在这种情况下,可读性/可维护性并不是最重要的。当然,如果它是可读/可维护的,那么我们应该去争取它,但如果可读性/可维护性损害性能,那么我们不应该使用这样的解决方案。
  • @geza 可读性可能是主观的,但在这种特殊情况下,我认为它客观地更具可读性和可维护性。除了命令式与声明式之外,这个版本让您不必考虑琐碎的可复制类型、非一错误和未对齐的读取,所有这些都极易出错。关于性能的话题,由于我们没有确切的使用情况,所以我几乎无法在任何方面争论。
【解决方案2】:

另一种方法是确保复制 16 字节的块。这样,副本可以根据一次复制 16 个字节的指令进行更好的优化(没有任何周围的随机播放或其他不必要的复杂性),如果它们存在(它们存在于 x64 上,例如 Xbox One 和 PC,这会有所帮助)。 PointXYZs 将是 16 个字节,因此向它们写入 16 个字节就可以了。源具有 12 个字节的元素,因此每次以这种方式复制其中一个元素时,其中的下一个元素也有 4 个字节,它们最终在目标 PointXYZ 的填充中,将被忽略。 last 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

【讨论】:

  • 展开循环不会有一点帮助吗?我看到铿锵声展开它,但 MSVC 没有,一个 OP 可能使用 MSVC。
  • @geza 它应该会有所帮助,因为 MSVC 在那里有点傻
  • 顺便说一句,在这种情况下,确切的实现是否重要?输入数据约为 24MB,整个过程很可能是内存带宽受限的,不是吗?
猜你喜欢
  • 2012-07-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-23
  • 2013-06-03
  • 2023-03-27
  • 1970-01-01
  • 2013-06-12
相关资源
最近更新 更多