到目前为止,我使用了 3 个 NOP 来“清理”管道。最近我遇到了 ISB 指令,它可以帮我做到这一点。查看arm信息中心,我注意到这个命令需要4个周期(在Cortex M0下),而3个NOP只需要3个周期。
我为什么要使用这个命令?它与 3 NOP 有什么不同?
我读过有关内存屏障如何工作的不同内容。
例如,用户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
据我所知,cpp11中原子类型的原子操作保证是aomtic。但是,假设在多核系统中,如果两个线程同时进行以下操作,结果会是1吗?(假设最初atomic<int> val=0;)看起来结果肯定是2,但为什么呢?
val.fetch_add(1,std::memory_order_relaxed);
作为补充,假设另一种情况,如果 thread1 do val.load(2);thread2 do val.load(3),看起来结果是 2 还是 3,但也不确定是哪一个。
我正在尝试将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)
这里的问题是我知道 …
我想在原子和非原子操作之间使用独立的内存屏障(我认为无论如何它都不重要)。我想我了解存储屏障和加载屏障的含义以及 4 种可能的内存重新排序;LoadLoad, StoreStore, LoadStore, StoreLoad.
但是,我总是发现获取/释放概念令人困惑。因为在阅读文档时,acquire 不仅说到loads,还说到stores,而release 不仅说到stores,还说到loads。另一方面,普通负载障碍仅为您提供负载保证,而普通商店障碍仅为您提供商店保证。
我的问题如下。在 C11/C++11 中,将独立atomic_thread_fence(memory_order_acquire)视为负载屏障(防止LoadLoad重新排序)和atomic_thread_fence(memory_order_release)存储屏障(防止StoreStore重新排序)是否安全?
如果以上是正确的,我可以用什么来防止LoadStore和StoreLoad重新排序?
当然,我对可移植性感兴趣,我不在乎上述在特定平台上产生什么。
参考下面的代码
auto x = std::atomic<std::uint64_t>{0};
auto y = std::atomic<std::uint64_t>{0};
// thread 1
x.store(1, std::memory_order_release);
auto one = y.load(std::memory_order_seq_cst);
// thread 2
y.fetch_add(1, std::memory_order_seq_cst);
auto two = x.load(std::memory_order_seq_cst);
Run Code Online (Sandbox Code Playgroud)
这里有可能one和two都为 0 吗?
(我似乎遇到了一个错误,在上面的代码运行后,如果one和two都可以保持 0 的值,则可以解释该错误。并且排序规则太复杂,我无法弄清楚上面可以进行哪些排序。)
我知道除了编译器之外,处理器还可以重新排序指令。
我有几个问题想不通。
假设我们有三个指令:
节目单
S1 S2 S3
处理器重新排序后,顺序变为(无论出于何种原因):
S3 S2 S1
对此的任何想法都受到高度赞赏。
读完“行动中的并发”后,我找不到一个问题的答案 - 当我们在一个原子变量上有一个存储(释放)和许多加载(获取)时,读取副作用的标准是否有保证?假设我们有:
int i{};
atomic<bool> b{};
void writer(){
i=42;
b.store(true,memory_order_release);
}
void reader(){
while(!b.load(memory_order_acquire))
this_thread::yield();
assert(i==42);
}
//---------------------
thread t1{writer},t2{reader},t3{reader};
Run Code Online (Sandbox Code Playgroud)
如果我们只有一个读取器,一切都可以,但是我们是否可以在 t2 或 t3 线程中断言失败?
(编辑:只是为了澄清:“缓存一致性”的问题是在不使用原子变量的情况下。)
是否有可能(单CPU情况:Windows可以在Intel / AMD / Arm CPU上运行),线程1运行在core-1上存储一个bool变量(例如)并且它保留在L1缓存中,而线程2在 core-n 上运行使用该变量,并且它会查找内存中该变量的另一个副本?
代码示例(为了演示该问题,我们假设这std::atomic_bool只是一个普通的bool):
#include <thread>
#include <atomic>
#include <chrono>
std::atomic_bool g_exit{ false }, g_exited{ false };
using namespace std::chrono_literals;
void fn()
{
while (!g_exit.load(std::memory_order_acquire))
{
// do something (lets say it takes 1-4s, repeatedly)
std::this_thread::sleep_for(1s);
}
g_exited.store(true, std::memory_order_release);
}
int main()
{
std::thread wt(fn);
wt.detach();
// do something (lets say it took 2s)
std::this_thread::sleep_for(2s);
// Exit
g_exit.store(true, std::memory_order_release);
for (int i = 0; i < 5; …Run Code Online (Sandbox Code Playgroud) c++ multithreading cpu-architecture memory-barriers stdatomic
据我所知,函数调用充当编译器障碍,但不作为CPU障碍.
本教程说明如下:
获取锁意味着获取语义,而释放锁意味着释放语义!其间的所有内存操作都包含在一个漂亮的小屏障三明治中,防止任何不希望的内存重新排序跨越边界.
我假设上面的引用是关于CPU重新排序而不是编译器重新排序.
但我不明白互斥锁和解锁如何导致CPU赋予这些函数获取和释放语义.
例如,如果我们有以下C代码:
pthread_mutex_lock(&lock);
i = 10;
j = 20;
pthread_mutex_unlock(&lock);
Run Code Online (Sandbox Code Playgroud)
上面的C代码被翻译成以下(伪)汇编指令:
push the address of lock into the stack
call pthread_mutex_lock()
mov 10 into i
mov 20 into j
push the address of lock into the stack
call pthread_mutex_unlock()
Run Code Online (Sandbox Code Playgroud)
现在是什么阻止了CPU重新排序mov 10 into i以及mov 20 into j 上方call pthread_mutex_lock()或下方call pthread_mutex_unlock()?
如果它是call阻止CPU进行重新排序的指令,那么为什么我引用的教程使它看起来像是互斥锁和解锁函数来阻止CPU重新排序,为什么我引用的教程没有说任何函数调用会阻止CPU重新排序吗?
我的问题是关于x86架构.
memory-barriers ×10
c++ ×5
assembly ×4
stdatomic ×4
memory-model ×3
atomic ×2
c ×2
x86 ×2
arm ×1
c++11 ×1
c++20 ×1
concurrency ×1
cortex-m ×1
gcc ×1
instructions ×1
mutex ×1
rdtsc ×1
windows ×1
x86-64 ×1