Hardware Lock Elision

XACQUIRE和XRELEASE两个接口。

XACQUIRE放在获取锁之前,作为一个提示。获取锁的时候,硬件不会去修改锁的地址上的数值;也不会把锁的地址放到自己的write set里面;相反,是放在read set中。所以其他人依旧可以获得锁。硬件会自动检测冲突的情况,如果有冲突就回滚。因为在互斥域内部,不会再修改锁,所以锁一直不会变的。

然而,为了在冲突的时候决定顺序,硬件自己会记住获取锁的顺序。而且在互斥域内部,如果想要读锁的值,会跟它已经获得锁的值是一样的。比如,获得锁都是给锁+1,刚开始的时候大家看到都是0。一个processor加锁之后,其他人看到仍然是0,自己看到的是1。

同样的道理,在释放锁的时候,如果试图恢复锁之前的值,那么硬件什么也不需要做,因为它之前也没改什么。

总之,如果多个线程抢一把锁,但是具体数值又完全不冲突,那么HLE可以让他们一起执行。

Restricted Transactional Memory

和一般意义上的TM一样,用了一组新的指令,XBEGIN, XEND, XABORT,提供开始、结束和取消transaction的功能。

如果失败了,会返回到xbegin那一行指令。只不过用eax寄存器来记录状态码。可以根据状态码决定继续执行的策略。

HLE对锁的要求

HLE的原理是把锁的地址放到read set里(如果是write set肯定有冲突了)。所以必须满足下面的条件:

  1. XRELEASE时候,要求它修饰的指令必须把锁的值恢复到它获得之前的值。所以acqure和release的数据大小、地址必须一致,而且锁千万不能跨cache line。
  2. 互斥域中,不能修改锁的值。

而且需要注意的一点是,冲突检测是按照cache line来的,所以如果锁和某个数据在同一个cache line,也会造成冲突(所谓的躺枪)

Nesting

有可能HLE或者RTM包了不止一层。对于HLE,如果超出上限了,内层的锁就按照正常的锁(互斥)去做。RTM其实没有限制,它只不过把多个xbegin/xend看成最外面的一个。

RTM abort的返回码

_xbegin()有个返回码,前6位是有意义的;分别表示是否被主动abort、可以retry、conflict导致回滚、buffer满了、debug的原因、nested的原因。最后8位是可以xabort的参数。

实际上,也有其他原因导致失败,所以返回了全0.

RTM Memory Ordering

这就涉及到两个方面的问题,一个不同的TM之间,首先他们内部是一个原子性操作;其次是彼此之间是严格有序的,如同锁一般。另外一个问题是xbegin前后,是没有memory fence的。但是如果abort的话,确保所有的xbegin和xend之间的数据都被回滚。

可能会造成abort的原因

指令

对通用浮点寄存器的操作,或者SIMD的XMM/YMM寄存器(SSE/AVX),一般来说不会触发abort的。但是SSE/AVX混着用可能会触发abort。

REP/REPNE等string operations的前缀,有可能因为string过长而导致abort;另外CLD/STD如果改了DF,(也就是string operations的方向),也会造成abort;不改没事。

xbort/CPUID/PAUSE肯定会造成abort。

  1. MMX/x87指令集;
  2. 更改EFLAGS寄存器的指令。
  3. 更改段寄存器的。
  4. SYSENTER, SYSCALL, SYSEXIT, SYSRET
  5. TLB修改;CFLUSH、INVD等控制cache的指令
  6. 保存处理器信息的指令: XSAVE
  7. 中断:INTO, INTn
  8. IO:

Runtime

一些非指令的原因也会造成abort,典型的就是各种exception/trap,并且值得注意的是,当transaction回滚时,就当这些trap从来没有发生过。

并且只支持write back类型的cache。如果用write through、uncacheable 等,就会自动abort。