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

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

但是,我想避免动态内存分配.因此,这样的事情看起来很自然(忽略了现在的对齐问题,可以使用aligned_storage或alignas来避免):

struct Hidden {
  char data[sz];

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

然后可以在单个翻译单元中定义Hidden :: punned以将数据转换为BHP *,对其执行操作并且不会污染具有170k LOC头文件的所有其他翻译单元.可能的实现可能是

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

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

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调用.不幸的是,情况并非如此,它们仍然存在并且比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)也完全抱怨违反严格别名规则,但并非所有版本都这样做.我还插入了一个了解BHP的Direct类,因此可以直接调用它,但我想避免这种情况.

最小的工作示例:

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

解决方法

This,of course,is undefined behaviour,because we access an object of type BHP through a pointer of type char.

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

new (data) BHP(...);

那么这是完全可以的:

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

只需确保您的char数组也是alignas(BHP).

请注意,当你有时重新解释char []时,gcc不喜欢,所以你可以选择使用像std::aligned_storage_t这样的东西.

相关文章

一.C语言中的static关键字 在C语言中,static可以用来修饰局...
浅谈C/C++中的指针和数组(二) 前面已经讨论了指针...
浅谈C/C++中的指针和数组(一)指针是C/C++...
从两个例子分析C语言的声明 在读《C专家编程》一书的第三章时...
C语言文件操作解析(一)在讨论C语言文件操作之前,先了解一下...
C语言文件操作解析(三) 在前面已经讨论了文件打开操作,下面...