我需要使用一个非常大且复杂的仅 header 类(想想boost::multiprecision::cpp_bin_float ,以下称为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-干线,Clang 3.9、4.0和5.0和Intel 18)都没有“优化”内存。 GCC的某些版本(例如5.3)也完全提示违反了严格的别名规则,尽管并非全部。我还插入了一个Direct类,它知道BHP,因此可以直接调用它,但我想避免这种情况。

最小的工作示例:
#include <cstring>

constexpr std::size_t sz = 64;

struct Base {
  char foo[sz];
  Base& operator+=(Base const& other) { foo[0] += other.foo[0]; return *this; }
};
typedef Base BHP;

// or:
//#include <boost/multiprecision/cpp_bin_float.hpp>
//typedef boost::multiprecision::number<boost::multiprecision::cpp_bin_float<76> > BHP;

struct Hidden {
  char data[sz];

  Hidden& proper(Hidden const& other);
  Hidden& punned(Hidden const& other);
};

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;
}

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

struct Direct {
  BHP member;
  Direct& direct(Direct const& other);
};

Direct& Direct::direct(Direct const& other) {
  member += other.member;
  return *this;
}

struct Pointer {
  char storage[sz];
  BHP* data;

  Pointer& also_ok(Pointer const& other);
};

Pointer& Pointer::also_ok(Pointer const& other) {
  *data += *other.data;
  return *this;
}

最佳答案



实际上并非如此。通过char* is fine进行访问,前提是实际上那里有一个BHP对象。也就是说,只要双方都拥有:

new (data) BHP(...);

那么这是完全可以的:
*(BHP*)(data) += *(BHP*)(other.data);

只需确保您的char数组也是alignas(BHP)即可。

请注意,gcc有时不喜欢在重新解释char[]时喜欢,因此您可以选择使用 std::aligned_storage_t 之类的东西。

关于c++ - 在尝试 ‘fast’ pimpl期间未优化memcpy,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/47815831/

10-11 21:01