movnt非临时存储与lock前缀原子RMW指令的交互及优化疑问
我开发了一个应用,使用movnt系列指令在常规(回写)内存上执行非临时写操作,之后将数据移交至另一个线程进行常规(回写)读取。Intel参考手册卷1指出,这类弱序存储需紧跟sfence或mfence指令。但我不清楚lock cmpxchg这类原子读-改-写(RMW)指令是否也能刷新非临时存储——已知这类指令是全内存屏障。Intel参考手册卷3提到,锁定操作会序列化所有未完成的加载和存储操作(仅弱序内存类型的加载除外)。
我认为lock前缀操作可替代写入线程的sfence,只要读取线程的访问与写入线程的lock操作同步即可,该解读是否正确?是否意味着包含原子RMW的mutex加锁/解锁会让sfence指令多余?反之,lock操作会刷新非临时存储所用的行填充缓冲区。我的代码可能每4KiB流式写操作就执行一次mutex解锁+加锁(原本无需sfence),该解读是否正确?这会对性能产生负面影响吗?
代码示例如下:
class ThreadSafeQueue { void push(MemoryBlock&&); MemoryBlock pop(); }; class SimpleQueue { void push(MemoryBlock&&); MemoryBlock pop(); }; void thread(SimpleQueue& input, ThreadSafeQueue& output) { std::mutex mutex; MemoryBlock tmp; while(MemoryBlock inblock = input.pop()) { // <-- no atomic or mutex mutex.lock(); // mutex for unrelated reasons (not shown) tmp.append(inblock); // append uses movnt, ca. 4 kiB at once if(tmp.full()) { // only once every few MiB _mm_sfence(); // <-- necessary if followed by mutex? output.push(std::move(tmp)); // <-- uses mutex tmp = MemoryBlock{}; } mutex.unlock(); // <-- memory barrier acts as unnecessary sfence? } }
总结疑问
- 若movnt弱序存储后紧跟原子RMW,是否可省略sfence?
- 若movnt指令间原本无需sfence,是否应避免插入原子RMW?
问题1:movnt后紧跟原子RMW可省略sfence吗?
完全可以。根据Intel手册卷3的说明,LOCK前缀的指令会强制序列化所有未完成的存储操作(包括movnt这类弱序存储),并且会刷新所有挂起的存储缓冲区(包括movnt用到的行填充缓冲区(Line Fill Buffer, LFB))。这意味着LOCK指令的内存屏障强度已经覆盖了SFENCE的作用——SFENCE仅保证弱序存储的全局可见性,而LOCK指令不仅能做到这一点,还会序列化所有加载/存储操作(除弱序内存的加载外)。
只要读取线程通过与LOCK操作同步(比如通过同一mutex的lock/unlock,或者原子变量的acquire语义访问)来观察数据,那么movnt写入的数据就能保证对读取线程可见,此时sfence完全多余。比如你代码中output.push调用的mutex操作,本身就包含LOCK前缀的原子RMW,这时候之前的_mm_sfence()可以直接删掉。
问题2:movnt指令间原本无需sfence,是否应避免插入原子RMW?
是的,应该尽量避免。原因有两点:
- 性能开销大:LOCK指令需要总线锁或者缓存锁,相比SFENCE(仅内存屏障,无总线交互),延迟高得多——SFENCE通常只需要几个周期,而LOCK指令可能需要几十甚至上百个周期,还可能引发缓存一致性流量。
- 不必要的序列化:movnt指令之间本身不需要同步——因为它们都是弱序存储,只要最终在数据对外暴露前完成同步即可。频繁插入LOCK指令会打乱CPU的乱序执行优化,导致movnt的流式写入效率下降,反而失去了movnt原本的性能优势(绕过缓存、减少写回压力)。
回到你的代码:每次4KiB movnt写入后就执行mutex unlock/lock,这完全是画蛇添足。这种频繁的LOCK操作会严重拖慢movnt的写入速度,建议只在需要把数据移交其他线程的时候(比如tmp.full()时),通过一次LOCK操作(或者SFENCE+同步操作)来完成可见性保证即可,中间的mutex操作如果是无关需求,最好调整逻辑避免。
内容的提问来源于stack exchange,提问作者Homer512

