memcpy在尝试"快速"pimpl时未优化
memcpy not optimised out during attempt at ‘fast’ pimpl
我需要使用一个非常大且复杂的仅头部的类(想想boost::multiprecision::cpp_bin_float<76>,下面称为BHP
),我想把它隐藏在类似于pimpl的实现后面,纯粹是为了在一个稍大的项目中减少编译时间(用std::complex<double>
替换boost类可以减少大约50%的编译时间)。
但是,我希望避免动态内存分配。因此,这样的事情看起来很自然(暂时忽略使用aligned_storage
或alignas
可以避免的对齐问题):
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;
}
当然,这是未定义的行为,因为我们通过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
调用可能会被优化。不幸的是,事实并非如此,它们仍然存在,使proper()
比punned()
大得多。
我想知道a)将数据直接存储在Hidden
对象中,b)避免不必要的副本来重新解释它,c)避免违反严格的对齐规则,d)不要携带指向存储区域的额外指针的正确方法。
这里有一个godbolt链接;请注意,我测试的所有编译器(GCC 4.9-trunk、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类型的指针访问
BHP
类型的对象。
事实并非如此。如果实际上有一个BHP
对象,则通过char*
进行访问是可以的。也就是说,只要双方都有:
new (data) BHP(...);
那么这完全可以:
*(BHP*)(data) += *(BHP*)(other.data);
只需确保您的char数组也是alignas(BHP)
。
请注意,gcc有时不喜欢重新解释char[]
,因此您可以选择使用类似std::aligned_storage_t
的内容。
- 空基优化子对象的地址
- 关闭||运算符优化
- 如何解决gcc编译器优化导致的centos双编译器设置中的分段错误
- 返回值优化:显式移动还是隐式
- 人脸跟踪arduino代码的优化
- 使用仅使用一次的变量调用的复制构造函数.这可能是通过调用move构造函数进行编译器优化的情况吗
- 纯函数,为什么没有优化
- 为什么大多数 pair 实现默认不使用压缩(空基优化)?
- 如何以优化的方式同时迭代两个间距不相等的数组
- 小字符串优化(调试与发布模式)
- 浮点定向舍入和优化
- Visual Studio 调试优化如何工作?
- 为什么开关的优化方式与 c/c++ 中的链接不同?
- 线性优化目标函数中的绝对值
- GCC 会优化内联访问器吗?
- gcc 如何优化此循环?
- 如何防止 CUDA-GDB 中的<优化输出>值
- 为什么我的程序在 O0 和 O2 的优化级别返回不同的结果
- 这个C++编译器优化(在自身的实例上调用对象自己的构造函数)的名称是什么,它是如何工作的?
- 使用 std::p air 进行返回值优化