首先我想谈一下我对此的一些理解,如有错误请指正。
MFENCE在x86中可以保证全屏障顺序一致性可防止 STORE-STORE、STORE-LOAD、LOAD-STORE 和 LOAD-LOAD 重新排序
这是根据维基百科的说法。
std::memory_order_seq_cst不保证防止 STORE-LOAD 重新排序。
这是根据Alex 的回答,“负载可能会通过早期存储重新排序到不同位置”(对于 x86),并且 mfence 不会总是被添加。
a是否std::memory_order_seq_cst表示顺序一致性?根据第2/3点,我认为这似乎不正确。std::memory_order_seq_cst仅当以下情况时才表示顺序一致性
MFENCE添加到任一LOAD或STORE否则仍有可能重新订购。
根据@LWimsey的评论,我在这里犯了一个错误,如果 和LOAD都是STORE,memory_order_seq_cst则没有重新排序。Alex 可能指出使用非原子或非 SC 的情况。
std::atomic_thread_fence(memory_order_seq_cst)总是产生一个完整的屏障
这是根据Alex的回答。所以我总是可以替换asm volatile("mfence" ::: "memory")为std::atomic_thread_fence(memory_order_seq_cst)
这对我来说很奇怪,因为memory_order_seq_cst原子函数和栅栏函数之间的用法似乎有很大不同。
现在我在MSVC 2015的标准库的头文件中找到这段代码,它实现了std::atomic_thread_fence
inline void _Atomic_thread_fence(memory_order _Order)
{ /* …Run Code Online (Sandbox Code Playgroud) 我正在运行x86,我想看到我的机器上无序执行导致的错误.根据这篇维基文章,我尝试写一篇,但我总是看到"x的值是33":
#include<stdio.h>
#include<pthread.h>
#include <sys/types.h>
int x, f;
void *handler(void *ptr) {
while (f == 0);
// Expectation: Sometimes, this should print 11 due to out-of-order exec
printf("value of x is %d \n", x);
return NULL;
}
int main() {
pthread_t thread1;
while(1) {
x = 11; f = 0;
pthread_create(&thread1, NULL, handler, NULL);
x = 33;
f = 1;
pthread_join(thread1, NULL);
}
return 0;
}
Run Code Online (Sandbox Code Playgroud)
什么是最简单的c程序可以说明乱序执行错误?为什么这有时不打印"x的值是11"?
unsigned int lo = 0;
unsigned int hi = 0;
__asm__ __volatile__ (
"mfence;rdtsc" : "=a"(lo), "=d"(hi) : : "memory"
);
Run Code Online (Sandbox Code Playgroud)
mfence 在上面的代码中,是否有必要?
根据我的测试,找不到cpu重新排序.
测试代码片段包含在下面.
inline uint64_t clock_cycles() {
unsigned int lo = 0;
unsigned int hi = 0;
__asm__ __volatile__ (
"rdtsc" : "=a"(lo), "=d"(hi)
);
return ((uint64_t)hi << 32) | lo;
}
unsigned t1 = clock_cycles();
unsigned t2 = clock_cycles();
assert(t2 > t1);
Run Code Online (Sandbox Code Playgroud) 我已经发现,在x86 CPU有以下内存屏障指令:mfence,lfence,和sfence。
x86 CPU是否仅具有这三个内存屏障指令,或者还有更多指令?
我读过有关内存屏障如何工作的不同内容。
例如,用户Johan在这个问题中的回答说,内存屏障是 CPU 执行的指令。
虽然用户Peter Cordes在这个问题中的评论说了以下关于 CPU 如何重新排序指令的内容:
它的读取速度比执行速度快,因此它可以看到即将到来的指令的窗口。有关详细信息,请参阅 x86 标签 wiki 中的一些链接,例如 Agner Fog 的 microarch pdf,以及 David Kanter 对 Intel Haswell 设计的文章。当然,如果您只是用谷歌搜索“乱序执行”,您会找到您应该阅读的维基百科文章。
所以我根据上面的评论猜测,如果指令之间存在内存屏障,CPU将看到这个内存屏障,这导致CPU不会对指令重新排序,所以这意味着内存屏障是一个“标记”让CPU看到而不是执行。
现在我的猜测是,内存屏障既充当标记又充当 CPU 执行的指令。
对于标记部分,CPU 看到指令之间存在内存屏障,这导致 CPU 不会对指令进行重新排序。
至于指令部分,CPU会执行内存屏障指令,这会导致CPU做一些诸如刷新存储缓冲区之类的事情,然后CPU会继续执行内存屏障之后的指令。
我对么?
x86 assembly instruction-set cpu-architecture memory-barriers
假设我让atomic<int> i;线程 A 使用 memory_order_release 执行原子存储/交换。接下来,线程 B 使用 memory_order_release 执行原子存储。线程 C 执行原子 fetch_add(0, memory_order_acquire);
线程 C 是否从线程A 和 B或仅从线程 B获取依赖项?
该mfence 文件说以下内容:
对MFENCE指令之前发出的所有内存加载和存储到内存指令执行序列化操作.此序列化操作保证在遵循MFENCE指令的任何加载或存储指令之前,按程序顺序在MFENCE指令之前的每个加载和存储指令都变为全局可见.
据我所知,x86中没有fence指令可以防止非读取和非写入指令的重新排序.
现在如果我的程序只有一个线程,即使指令被重新排序,它仍然看起来好像指令正在按顺序执行.
但是,如果我的程序有多个线程,并且在其中一个线程中非读取和非写入指令被重新排序,其他线程会注意到这个重新排序(我假设答案是否定,否则会有一个fence指令停止非读取和非写入指令重新排序,或者我可能缺少某些东西)?
具体是:
mov %eax, %ds
Run Code Online (Sandbox Code Playgroud)
慢于
mov %eax, %ebx
Run Code Online (Sandbox Code Playgroud)
或者他们是相同的速度.我在网上研究过,但一直无法找到明确的答案.
我不确定这是否是一个愚蠢的问题,但我认为修改分段寄存器可以使处理器做额外的工作.
NB我关注旧的x86 linux cpus,而不是现代的x86_64 cpus,其中分段的工作方式不同.
我正在尝试将clock_gettime(CLOCK_REALTIME, &ts) 替换为rdtsc,以CPU 周期而不是服务器时间来衡量代码执行时间。基准测试代码的执行时间对于软件至关重要。我尝试在独立核心上的 x86_64 3.20GHz ubuntu 机器上运行代码并得到以下数字:
情况 1:时钟获取时间: 24 纳秒
void gettime(Timespec &ts) {
clock_gettime(CLOCK_REALTIME, &ts);
}
Run Code Online (Sandbox Code Playgroud)
情况 2:rdtsc(没有 mfence 和编译器屏障): 10 ns
void rdtsc(uint64_t& tsc) {
unsigned int lo,hi;
__asm__ __volatile__ ("rdtsc" : "=a" (lo), "=d" (hi));
tsc = ((uint64_t)hi << 32) | lo;
}
Run Code Online (Sandbox Code Playgroud)
情况 3:rdtsc(带有 mfence 和编译器屏障): 30 ns
void rdtsc(uint64_t& tsc) {
unsigned int lo,hi;
__asm__ __volatile__ ("mfence;rdtsc" : "=a" (lo), "=d" (hi) :: "memory");
tsc = ((uint64_t)hi << 32) | lo;
}
Run Code Online (Sandbox Code Playgroud)
这里的问题是我知道 …
我提出了一个想法,我试图实现一个无锁堆栈,不依赖于引用计数来解决ABA问题,并且还正确处理内存回收.它在概念上与RCU类似,并且依赖于两个特征:将列表条目标记为已删除,以及跟踪阅读器遍历列表.前者很简单,它只使用指针的LSB.后者是我对实现无限制无锁堆栈的方法的"聪明"尝试.
基本上,当任何线程试图遍历列表时,一个原子计数器(list.entries)会递增.遍历完成后,第二个计数器(list.exits)递增.
节点分配由push处理,释放由pop处理.
推送和弹出操作与天真无锁堆栈实现非常相似,但必须遍历标记为删除的节点才能到达未标记的条目.因此推送基本上就像链表插入一样.
pop操作类似地遍历列表,但它使用atomic_fetch_or将节点标记为在遍历时被删除,直到它到达未标记的节点.
遍历0个或更多标记节点的列表后,弹出的线程将尝试CAS堆栈的头部.至少有一个并发弹出的线程将成功,在此之后,进入堆栈的所有读者将不再看到以前标记的节点.
成功更新列表的线程然后加载原子list.entries,并基本上自旋加载atomic.exits,直到该计数器最终超过list.entries.这应该意味着列表的"旧"版本的所有读者都已完成.然后,该线程只是释放它从列表顶部交换的标记节点列表.
因此,弹出操作的含义应该(我认为)可能没有ABA问题,因为释放的节点不会返回到可用的指针池,直到所有使用它们的并发读取器完成,显然内存回收问题由于同样的原因,处理也是如此.
所以,无论如何,这是理论,但我仍然在实现上摸不着头脑,因为它目前无法正常工作(在多线程情况下).似乎我在免费问题之后得到了一些写作,但是我在查找问题时遇到了麻烦,或者我的假设可能存在缺陷而且无法正常工作.
无论是概念还是调试代码的方法,都会非常感谢任何见解.
这是我当前(损坏的)代码(使用gcc -D_GNU_SOURCE -std = c11 -Wall -O0 -g -pthread -o list list.c编译):
#include <pthread.h>
#include <stdatomic.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include <sys/resource.h>
#include <stdio.h>
#include <unistd.h>
#define NUM_THREADS 8
#define NUM_OPS (1024 * 1024)
typedef uint64_t list_data_t;
typedef struct list_node_t {
struct list_node_t * _Atomic next;
list_data_t data;
} list_node_t;
typedef struct {
list_node_t * _Atomic head;
int64_t _Atomic size;
uint64_t _Atomic entries;
uint64_t _Atomic exits;
} list_t; …Run Code Online (Sandbox Code Playgroud) 这是一个后续问题
有很多文章和博客提到 Java 和 JVM 指令重新排序,这可能会导致用户操作中出现反直觉的结果。
当我要求演示 Java 指令重新排序导致意外结果时,有几条评论说,更普遍的关注领域是内存重新排序,并且很难在 x86 CPU 上进行演示。
指令重新排序只是内存重新排序、编译器优化和内存模型等更大问题的一部分吗?这些问题真的是 Java 编译器和 JVM 特有的吗?它们是否特定于某些 CPU 类型?
java cpu-architecture memory-barriers instruction-reordering
我想研究最新的处理器与标准 RISC V 实现(RISC V 具有 5 级管道 - 提取、解码、内存、ALU、回写)有何不同,但无法找到我应该如何开始解决问题以找到当前处理器流水线的实现
我尝试参考 i7-4510U 文档的英特尔文档,但没有太大帮助