memcpy在尝试“快速”pimpl期间没有优化

时间:2017-12-14 14:34:02

标签: c++ memcpy strict-aliasing

我需要使用一个非常大且复杂的仅限标题的类(想想boost :: multiprecision :: cpp_bin_float&lt; 76&gt;,下面称为BHP),我想隐藏在类似pimpl的实现背后,纯粹是为了减少一个大型项目中的编译时间(用std::complex<double>替换Boost类减少了大约50%的编译时间。)

但是,我想避免动态内存分配。因此,这样的事情似乎很自然(暂时忽略对齐问题,使用aligned_storagealignas可以避免这种问题):

struct Hidden {
  char data[sz];

  Hidden& punned(Hidden const& other);
};
然后可以在单个翻译单元中定义

Hidden::punned以将data强制转换为BHP*,对其执行操作并且不会污染具有170k LOC头文件的所有其他翻译单元。可能的实现可能是

Hidden& Hidden::punned(Hidden const& other) {
  *(BHP*)(data) += *(BHP*)(other.data);
  return *this;
}

当然,这是未定义的行为,因为我们通过类型为BHP的指针访问类型为char的对象,因此违反了严格的别名规则。正确的方法是:

Hidden& Hidden::proper(Hidden const& other) {
  BHP tmp; std::memcpy(&tmp, data, sz);
  BHP tmp2; std::memcpy(&tmp2, other.data, sz);
  tmp += tmp2;
  std::memcpy(data, &tmp, sz);
  return *this;
}

现在看来“显而易见”可以优化这些memcpy次调用。不幸的是,情况并非如此,它们仍然存在并使proper()远大于punned()

我想知道a)将数据直接存储在Hidden对象中的正确方法是什么,b)避免不必要的副本重新解释它和c)避免违反严格对齐规则和d )不要携带指向存储区域的额外指针。

有一个godbolt link here;请注意,我测试的所有编译器(GCC 4.9 - trunk,Clang 3.9,4.0和5.0以及Intel 18)都没有“优化”memcpy。某些版本的GCC(例如5.3)也完全抱怨违反严格别名规则,但并非所有版本都这样做。我还插入了一个知道Direct的{​​{1}}类,因此可以直接调用它,但我想避免这种情况。

最小的工作示例:

BHP

1 个答案:

答案 0 :(得分:1)

  

当然,这是未定义的行为,因为我们通过char类型的指针访问类型为char*的对象。

事实并非如此。通过BHP is fine 进行访问,前提是实际上存在new (data) BHP(...); 个对象。也就是说,只要双方都有:

*(BHP*)(data) += *(BHP*)(other.data);

然后这是完全可以的:

alignas(BHP)

确保您的char数组也是char[]

请注意,gcc在您有时重新解释{{1}}时不喜欢,因此您可以选择使用std::aligned_storage_t之类的内容。