X86 原子_ref和带填充位的原子的正确和最佳实现是什么?

X86 原子_ref和带填充位的原子的正确和最佳实现是什么?,x86,c++20,micro-optimization,stdatomic,X86,C++20,Micro Optimization,Stdatomic,TL;DR:应该std::atomicCAS在构造函数中使用填充位零填充位,还是通过CAS重试/掩码LL/SC处理它们?还考虑原子> RIF> CAS,原子等待“原子< /代码>和原子ICRIF> < /代码> ./P> P> C++中的一些平凡类型不具有UNIQUY表示, STD::HasuUnjyObjultObjultAssixsV对它们是错误的,也就是说,等价的值可能不 MMECMP < /C>位相等。 原因之一是有填充位。填充位是未使用的位字段位、结构对齐字节或额外填充,例如10

TL;DR:应该
std::atomic
CAS在构造函数中使用填充位零填充位,还是通过CAS重试/掩码LL/SC处理它们?还考虑<代码>原子> RIF> <代码> CAS,原子等待“<代码>原子< /代码>和<代码>原子ICRIF> < /代码> ./P>
<> P> C++中的一些平凡类型不具有UNIQUY表示,<代码> STD::HasuUnjyObjultObjultAssixsV对它们是错误的,也就是说,等价的值可能不<代码> MMECMP < /C>位相等。

原因之一是有填充位。填充位是未使用的位字段位、结构对齐字节或额外填充,例如10字节浮点

C++2a排除了填充
atomic::compare_exchange_strong
compare_exchange_弱
比较。见:

  • 主要提案
  • 来自原子参考的链接

现在的问题是,如何才能正确有效地实现这一点,特别是考虑到拥有
atomic\u ref
。x86在CAS中只支持位相等,我希望它与其他CPU架构类似

  • 一种方法是在构造函数中清除源值中的填充位,
    存储
    交换
    ,并在
    比较交换*
    中清除所需的值。这样看来,
    atomic\u-ref
    构造函数必须是原子的,如果不使用compare\u-exchange操作,则会出现“为不使用的东西付费”的情况
  • 我看到的另一种方法是从
    compare\u exchange
    循环中的观察值复制填充位。所以CAS循环只会在值位不匹配时退出。这似乎违背了区分强CA和弱CA的目的,因为弱CA不应该总是失败,而使用这种方法可能会失败。虽然基于LL/SC的CA似乎能够与自然排除的填充位进行内部比较,但没有循环的弱CA是可能的
因此,这里的问题是:

  • 以下哪种方法(如果有)是正确的?还有其他正确的方法吗
  • 如果多种方法都是正确的,那么通常哪种方法更有效
请注意,
atomic::wait
/
atomic\u ref::wait
还必须处理填充位,并且该方法必须共享


请注意,有一种获取非零填充位的简单方法:

struct S {
  int i : 17;
};
S* s = (S*)malloc(sizeof(S));
s->i = 1;

struct S2 {
  std::uint8_t  j;
  std::uint16_t k;
};

S2* s2 = (S2*)malloc(sizeof(S2));
s2->j = 2;
...
s2->k = 3;


原子的构造函数应该总是简单的,不接触引用的对象。没有人想要额外的原子存储或RMW来清除填充位,因为它们不可能是非零的
atomic_ref
被设计为在每次您希望以原子方式访问对象时重新构造。它需要不断优化

此外,我们不想让使用非原子对象的代码变慢,以防某些地方可能会使用原子对象

(也就是说,如果填充是整数字节,则可以在CAS之前使用一个或多个普通存储将这些字节存储到。无任何内容(原始CAS指令除外)应始终取决于从这些填充位/字节中读取的值,因此对象表示是否存在撕裂的可能性并不重要。填充不是
t
值的一部分,因此无法撕裂该值。)


我看不到任何明确的方法来有效地实现
atomic_ref
的所有功能;具有非零填充位的对象可以很容易地发生。< /强>在硬件CAS的机器上很难实现这种ISO C++更改。对于现有的主流ISAS来说,任何一个不容易被支持的改变,历史上都是非常保守的,所以C++看起来很奇怪,除非有一些我认为没有看到的技巧。 在大多数情况下,使用现有行为不会有害,并且在CAS重试循环中使用对象的最后一次看到的值作为“期望值”时,可能会被“仿佛”规则所允许。原子的也是如此

但这不适用于创建新的
t
并将其用作CAS的“预期”参数的代码,或者每个CAS故障都有明显的副作用的代码


对于
atomic
(而不是
atomic_ref
),可能实现建议的C++20更改(CAS比较值,而不是memcmp对象表示),而不会影响非原子对象的性能:确保填充位/字节始终处于相同的规范状态,
0
是明显的选择

清理/规范化
原子
构造函数中的填充位,以及与
存储
交换
和CAS一起使用的每个新值中的填充位

C++20还将
std::atomic
的默认构造函数从琐碎(除了静态存储的zero init之外没有初始化)更改为(C++20)使用T()初始化底层对象的值,即原语类型为零。(C++20也不赞成使用,因为它是一个笨重的设计。)

所以我认为我们可以假设每个
std::atomic
对象都是由
std::atomic
构造函数构造的。可能的问题是,某些现有代码可能只是将指针投射到
原子*
,并在不使用新位置的情况下使用它。如果这是C++20中官方未定义的行为,那么这就是代码的问题(特别是如果它对CAS在带填充的T上的作用有任何期望的话)

C++20构造函数应该确保任何填充都是零的,而不仅仅是值位。进一步的原子操作不应该改变这一点,只要CAS确保
所需的
同样是规范的。和
exchange
store
如果输入包含任何填充位,则对其进行类似的清理/规范化

在x86-64上,我认为