[转]why memory barrier

什么是内存屏障? Why Memory Barriers ? 要了解如何使用memory barrier,最好的方法是明白它为什么存在。CPU硬件设计为了提高指令的执行速度,增设了两个缓冲区(store buffer, invalidate queue)。这个两个缓冲区可以避免CPU在某些情况下进行不必要的等待,从而提高速度,但是这两个缓冲区的存在也同时带来了新的问题。 1. cache一致性问题 Cache 一致性问题出现的原因是在一个多处理器系统中,每个处理器核心都有独占的Cache 系统(比如一级 Cache 和二级 Cache),而导致一个... 阅读详情

要了解如何使用memory barrier,最好的方法是明白它为什么存在。CPU硬件设计为了提高指令的执行速度,增设了两个缓冲区(store buffer, invalidate queue)。这个两个缓冲区可以避免CPU在某些情况下进行不必要的等待,从而提高速度,但是这两个缓冲区的存在也同时带来了新的问题。

要仔细分析这个问题需要先了解cache的工作方式。

目前CPU的cache的工作方式很像软件编程所使用的hash表,书上说“N路组相联(N-way set associative)”,其中的“组”就是hash表的模值,即hash链的个数,而常说的“N路”,就是每个链表的最大长度。链表的表项叫做 cache-line,是一段固定大小的内存块。读操作很直接,不再赘述。如果某个CPU要写数据项,必须先将该数据项从其他CPU的cache中移出, 这个操作叫做invalidation。当invalidation结束,CPU就可以安全的修改数据了。如果数据项在该CPU的cache中,但是是只 读的,这个过程叫做”write miss”。一旦CPU将数据从其他CPU的cache中移除,它就可以重复的读写该数据项了。如果此时其他CPU试图访问这个数据项,将产生一 次”cache miss”,这是因为第一个CPU已经使数据项无效了。这种类型的cache-miss叫做”communication miss”,因为产生这种miss的数据项通常是做在CPU之间沟通之用,比如锁就是这样一种数据项。

为了保证在多处理器的环境下cache仍然一致,需要一种协议来防止数据不一致和丢失。目前常用的协议是MESI协议。MESI是 Modified,Exclusive, Shared, Invalid这四种状态的首字母的组合。使用该协议的cache,会在每个cache-line前加一个2位的tag,标示当前的状态。

modified状态:该cache-line包含修改过的数据,内存中的数据不会出现在其他CPU-cache中,此时该CPU的cache中包含的数据是最新的
exclusive状态:与modified类似,但是数据没有修改,表示内存中的数据是最新的。如果此时要从cache中剔除数据项,不需要将数据写回内存
shared状态:数据项可能在其他CPU中有重复,CPU必须在查询了其他CPU之后才可以向该cache-line写数据
invalid状态:表示该cache-line空

MESI使用消息传递的方式在上述几种状态之间切换,具体转换过程参见[1]。如果CPU使用共享BUS,下面的消息足够:

read: 包含要读取的CACHE-LINE的物理地址
read response: 包含READ请求的数据,要么由内存满足要么由cache满足
invalidate: 包含要invalidate的cache-line的物理地址,所有其他cache必须移除相应的数据项
invalidate ack: 回复消息
read invalidate: 包含要读取的cache-line的物理地址,同时使其他cache移除该数据。需要read response和invalidate ack消息
writeback:包含要写回的数据和地址,该状态将处于modified状态的lines写回内存,为其他数据腾出空间

引用[1]中的话:

Interestingly enough, a shared-memory multiprocessor system really is a message-passing computer under the covers. This means that clusters of SMP machines that use distributed shared memory are using message passing to implement shared memory at two different levels of the system architecture.

虽然该协议可以保证数据的一致性,但是在某种情况下并不高效。举例来说,如果CPU0要更新一个处于CPU1-cache中的数据,那么它必须等待 cache-line从CPU1-cache传递到CPU0-cache,然后再执行写操作。cache之间的传递需要花费大量的时间,比执行一个简单的 操作寄存器的指令高出几个数量级。而事实上,花费这个时间根本毫无意义,因为不论从CPU1-cache传递过来的数据是什么,CPU0都会覆盖它。为了 解决这个问题,硬件设计者引入了store buffer,该缓冲区位于CPU和cache之间,当进行写操作时,CPU直接将数据写入store buffer,而不再等待另一个CPU的消息。但是这个设计会导致一个很明显的错误情况。

试考虑如下代码:

1 a = 1;
2 b = a + 1;
3 assert(b == 2);

假设初始时a和b的值都是0,a处于CPU1-cache中,b处于CPU0-cache中。如果按照下面流程执行这段代码:

1 CPU0执行a=1;
2 因为a在CPU1-cache中,所以CPU0发送一个read invalidate消息来占有数据
3 CPU0将a存入store buffer
4 CPU1接收到read invalidate消息,于是它传递cache-line,并从自己的cache中移出该cache-line
5 CPU0开始执行b=a+1;
6 CPU0接收到了CPU1传递来的cache-line,即“a=0”
7 CPU0从cache中读取a的值,即“0”
8 CPU0更新cache-line,将store buffer中的数据写入,即“a=1”
9 CPU0使用读取到的a的值“0”,执行加1操作,并将结果“1”写入b(b在CPU0-cache中,所以直接进行)
10 CPU0执行assert(b == 2); 失败

出现问题的原因是我们有两份”a”的拷贝,一份在cache-line中,一份在store buffer中。硬件设计师的解决办法是“store forwarding”,当执行load操作时,会同时从cache和store buffer里读取。也就是说,当进行一次load操作,如果store-buffer里有该数据,则CPU会从store-buffer里直接取出数 据,而不经过cache。因为“store forwarding”是硬件实现,我们并不需要太关心。

还有一中错误情况,考虑下面的代码:

void foo(void)
{
a = 1;
b = 1;
}

void bar(void)
{
while (b == 0) continue;
assert(a == 1);
}

假设变量a在CPU1-cache中,b在CPU0-cache中。CPU0执行foo(),CPU1执行bar(),程序执行的顺序如下:

1 CPU0执行 a = 1; 因为a不在CPU0-cache中,所以CPU0将a的值放到store-buffer里,然后发送read invalidate消息
2 CPU1执行while(b == 0) continue; 但是因为b不再CPU1-cache中,所以它会发送一个read消息
3 CPU0执行 b = 1;因为b在CPU0-cache中,所以直接存储b的值到store-buffer中
4 CPU0收到 read 消息,于是它将更新过的b的cache-line传递给CPU1,并标记为shared
5 CPU1接收到包含b的cache-line,并安装到自己的cache中
6 CPU1现在可以继续执行while(b == 0) continue;了,因为b=1所以循环结束
7 CPU1执行assert(a == 1);因为a本来就在CPU1-cache中,而且值为0,所以断言为假
8 CPU1收到read invalidate消息,将并将包含a的cache-line传递给CPU0,然后标记cache-line为invalid。但是已经太晚了

就是说,可能出现这类情况,b已经赋值了,但是a还没有,所以出现了b = 1, a = 0的情况。对于这类问题,硬件设计者也爱莫能助,因为CPU无法知道变量之间的关联关系。所以硬件设计者提供了memory barrier指令,让软件来告诉CPU这类关系。解决方法是修改代码如下:

void foo(void)
{
a = 1;
smp_mb();
b = 1;
}

smp_mb()指令可以迫使CPU在进行后续store操作前刷新store-buffer。以上面的程序为例,增加memory barrier之后,就可以保证在执行b=1的时候CPU0-store-buffer中的a已经刷新到cache中了,此时CPU1-cache中的a 必然已经标记为invalid。对于CPU1中执行的代码,则可以保证当b==0为假时,a已经不在CPU1-cache中,从而必须从CPU0- cache传递,得到新值“1”。具体过程见[1]。

上面的例子是使用memory barrier的一种环境,另一种环境涉及到另一个缓冲区,确切的说是一个队列——“Invalidate Queues”。

store buffer一般很小,所以CPU执行几个store操作就会填满。这时候CPU必须等待invalidation ACK消息,来释放缓冲区空间——得到invalidation ACK消息的记录会同步到cache中,并从store buffer中移除。同样的情形发生在memory barrier执行以后,这时候所有后续的store操作都必须等待invalidation完成,不论这些操作是否导致cache-miss。解决办法 很简单,即使用“Invalidate Queues”将invalidate消息排队,然后马上返回invalidate ACK消息。不过这种方法有问题。

考虑下面的情况:

void foo(void)
{
a = 1;
smp_mb();
b = 1;
}

void bar(void)
{
while (b == 0) continue;
assert(a == 1);
}

a处于shared状态,b在CPU0-cache内。CPU0执行foo(),CPU1执行函数bar()。执行操作如下:

1 CPU0执行a=1。因为cache-line是shared状态,所以新值放到store-buffer里,并传递invalidate消息来通知CPU1
2 CPU1执行 while(b==0) continue;但是b不再CPU1-cache中,所以发送read消息
3 CPU1接受到CPU0的invalidate消息,将其排队,然后返回ACK消息
4 CPU0接收到来自CPU1的ACK消息,然后执行smp_mb(),将a从store-buffer移到cache-line中
5 CPU0执行b=1;因为已经包含了该cache-line,所以将b的新值写入cache-line
6 CPU0接收到了read消息,于是传递包含b新值的cache-line给CPU1,并标记为shared状态
7 CPU1接收到包含b的cache-line
8 CPU1继续执行while(b==0) continue;因为为假所以进行下一个语句
9 CPU1执行assert(a==1),因为a的旧值依然在CPU1-cache中,断言失败
10 尽管断言失败了,但是CPU1还是处理了队列中的invalidate消息,并真的invalidate了包含a的cache-line,但是为时已晚

可以看出出现问题的原因是,当CPU排队某个invalidate消息后,在它还没有处理这个消息之前,就再次读取该消息对应的数据了,该数据此时本应该已经失效的。

解决方法是在bar()中也增加一个memory barrier:

void bar(void)
{
while (b == 0) continue;
smp_mb();
assert(a == 1);
}

此处smp_mb()的作用是处理“Invalidate Queues”中的消息,于是在执行assert(a==1)时,CPU1中的包含a的cache-line已经无效了,新的值要重新从CPU0-cache中读取。

memory bariier还可以细分为“write memory barrier(wmb)”和“read memory barrier(rmb)”。rmb只处理Invalidate Queues,wmb只处理store buffer。

可以使用rmb和wmb重写上面的例子:

void foo(void)
{
a = 1;
smp_wmb();
b = 1;
}

void bar(void)
{
while (b == 0) continue;
smp_rmb();
assert(a == 1);
}

最后提一下x86的mb。x86CPU会自动处理store顺序,所以smp_wmb()原语什么也不做,但是load有可能乱序,smp_rmb()和smp_mb()展开为lock;addl。

[1] http://www.rdrop.com/users/paulmck/scalability/paper/whymb.2010.06.07c.pdf
[2] http://en.wikipedia.org/wiki/Memory_barrier
[3] http://www.mjmwired.net/kernel/Documentation/memory-barriers.txt

传感器模块的电路艺术:从引脚定义到系统集成的设计哲学 本文深入探讨传感器模块的电路设计艺术,从引脚定义到系统集成的完整设计哲学。重点解析电源管理、信号处理及I2C通信协议等核心电路原理,分享多模块协同中的电源架构、信号完整性和接地策略,为硬件工程师提供实用的系统级设计思维与实战案例。 阅读详情

相关推荐

当兵心理测试软件,当兵心理测试题及答案

心理测试是一种比较先进的测试方法,它是指通过一系列手段,将人的某些心理特征数量化,来衡量个体心理因素水平和个体心理差异差异的一种科学测量方法。小编为你整理了当兵心理测试题及答案,希望对你有所参考帮助。征兵心理测试题(1代表是2代表否)1、我的胃口很好。12、我早上起来的时候,多半觉得睡眠充足,头脑清醒13、我喜欢看报纸上的犯罪新闻。14、有时我常感到困难重重,无法克服。25、我时常遇见一些所谓的专...

weixin_42128393的博客 1万+

代码优化和内存屏障(barrier、mb、rmb、wmb)

内存屏障主要解决的问题是编译器的优化和CPU的乱序执行。 编译器在优化的时候,生成的汇编指令可能和c语言程序的执行顺序不一样,在需要程序严格按照c语言顺序执行时,需要显式的告诉编译不需要优化,这在linux下是通过barrier()宏完成的,它依靠volatile关键字和memory关键字: /* Optimization barrier */ /* The "volatile...

7261

CANoe诊断测试避坑指南:DTC故障注入与ECU刷写失败的N个常见原因排查

本文深入解析CANoe诊断测试中的常见问题,重点探讨DTC故障注入与ECU刷写失败的排查策略。从会话管理、安全访问到数据完整性校验,提供实战经验与CAPL脚本示例,帮助工程师规避诊断测试中的典型陷阱,提升车载网络通信与ECU刷写的可靠性。

weixin_29278805的博客 461

Linux内核中的内存屏障

编译器有时会对代码做一些优化,例如尝试在保证程序执行正确的前提下修改指令顺序或优化ldr/str指令,让程序执行地更快。但是编译器毕竟不能完全猜透人的心思,有时候它做的优化会导致程序运行不符我们的预期。 因此,内核中提供了一些额外的函数,可以插在某段代码里,告诉编译器不要在这里做指令优化。这些函数分为两种: 内存屏障:rmb(), wmb(), mb(),可以防止硬件上的指令重排。除了编译器,...

去级得骨灰 958

4×V100部署Qwen3.8-Flash-Next:vLLM/GGUF实战

大模型本地部署的核心,是把参数量、量化精度、显存容量和推理框架放在同一张账本里算清楚。MoE 模型总参数量大但激活参数少,解码阶段更受显存带宽与 KV Cache 限制;通过 INT4/AWQ 或 GGUF 等 4-bit 量化可显著压缩权重占用,再结合 vLLM 张量并行、llama.cpp 分层切分提升多卡利用率。其技术价值在于让 V100 这类老架构 GPU 也能承载百亿级参数模型的私有化推理,并服务于 RAG 知识库、工作流 API、批量生成等场景。这次围绕 4×V100 32G 部署 Qwen3.

weixin_29061425的博客 347

14、利用Helm和GitOps实现Kubernetes资源管理与CI/CD流程优化

本文介绍了如何结合Helm、GitOps和Terraform等工具,优化Kubernetes资源管理和CI/CD流程。内容涵盖了Helm提供程序的配置、GitOps的核心理念、CI/CD管道的构建与测试策略、GitHub源代码管理模型以及流程优化建议,旨在提升软件开发和部署的效率与质量。

y7z8a9的博客 155

barrier(wmb,mb,rmb)和cache coherence

 http://www.linuxforum.net/forum/gshowflat.php?Cat=&Board=linuxK&Number=428239&page=5&view=collapsed&sb=5&o=all&fpart=注: 这里的barrier 指的是wmb, rmb, mb. 一 直找不到合适的资料说明barrier和 Cache coherence 之间的关系.

小春的药匣子 5671

yolov8 PTQ和QAT量化实战(源码详解)

量化():是指将高精度浮点数(FP32)转换为低精度(8bit或4bit)整数的过程,它是模型部署中的一种非常重要的优化方法,可以在较少的精度损失下,大幅提供神经网络的效率和性能。目前主流的神经网络量化方式有PTQ和QATPTQ是训练后量化(),无需数据集和训练,直接量化模型权重QAT是量化感知训练(),量化后需要在数据集上继续微调训练。QAT过程中,我们首先需要选择哪些层需要进行finetune训练,并利用FP32对应层的输出作为监督进行训练。

@bangbang的博客 5326

linux内存屏障浅析

内存屏障是一个很神奇的东西,之前翻译了linux内核文档memory-barriers.txt,对内存屏障有了一定有理解。现在用自己的方式来整理一下。 在我看来,内存屏障主要解决了两个问题:单处理器下的乱序问题和多处理器下的内存同步问题。 为什么会乱序 现在的CPU一般采用流水线来执行指令。一个指令的执行被分成:取指、译码、访存、执行、写回、等若干个阶段。然后,多条指令可以同时存在于流水

myxmu的专栏 1200

Linux iperf 进行跑流测试时,为啥单条流会有速率上不去的问题?

iperf

1042484520@qq.com(邮箱) 3086

出现 RuntimeError: adaptive_avg_pool2d_backward_cuda does not have a deterministic implementation的解决方法

大致就是在执行主函数的训练函数中,在反向传播这一块出现了问题。解决方法就是添加如下代码块。找到提示bug的行数。

码农研究僧的博客 5532

Linux linux mb() / rmb() / wmb()

linux mb()/rmb()/wmb() 2010-08-23 16:45:49 分类: LINUX 在阅读linux 2.6.23内核代码中遇到mb()/rmb()/wmb() 这几个宏,不明白如何使用, 在分析其汇编代码后,大概的了解了这和内存屏障有关,代码如下: #define X86_FEATURE_XMM2 (0*32+26)

chrovery的专栏 1万+

华为OD机考2025C卷 - 精准核酸检测(Java & Python& JS & C++ & C语言)

华为OD机考2025C卷 - 精准核酸检测,提供在线OJ Java & Python& JS & C++ & C

算法大师! 2882

【转】Why Memory Barrier?

要了解如何使用memory barrier,最好的方法是明白它为什么存在。CPU硬件设计为了提高指令的执行速度,增设了两个缓冲区(store buffer, invalidate queue)。这个两个缓冲区可以避免CPU在某些情况下进行不必要的等待,从而提高速度,但是这两个缓冲区的存在也同时带来了新的问题。 要仔细分析这个问题需要先了解cache的工作方式。 目前CPU的cache的

游牧的博客 697

最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版

Part-E1-SDIO-Specification-Ver4.00-Final-120220.pdf:最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版,包括SDIO 接口的外设,SDIO 接口官方协议等。

怀旧版电脑GBC模拟器

再现原版GBC掌机的界面,电脑运行连游戏界面都一样大小,很怀旧,很真实,(英文版)强烈推荐,很小的文件,很难才找到的。

Why Memory Barrier?

http://sstompkins.wordpress.com/2011/04/12/why-memory-barrier%EF%BC%9F/ 要了解如何使用memory barrier,最好的方法是明白它为什么存在。CPU硬件设计为了提高指令的执行速度,增设了两个缓冲区(store buffer, invalidate queue)。这个两个缓冲区可以避免CPU在某些情况下进行不必要的

larryliuqing的专栏-xxx 643

复分析 阿尔福斯(Ahlfors) 第三版答案 英文

答案分为十章,有部分习题没有答案,全英文复分析阿尔福斯(Ahlfors) 第三版答案

Why Memory Barrier

为什么需要Memory Barrier

huangzhipeng的专栏 2644

ssm框架搭建

这个是一个在idea上成功跑起来的ssm框架项目,保存在这里方便以后使用直接下载

Why Memory Barriers中文翻译(下)

转载自:http://www.wowotech.net/kernel_synchronization/why-memory-barrier-2.html 在上一篇why memory barriers文档中,由于各种原因,有几个章节没有翻译。其实所谓的各种原因总结出一句话就是还没有明白那些章节所要表达的内容。当然,对于一个真正的热爱钻研的linuxer,不理解的那些章节始终都是一块心病。终于,在一个月黑风高的夜晚,我发了一封邮件给perfbook的作者Paul,请其指点一二。果然是水平越高越平易近人.

厚积薄发者,轻舟万重山 1456

Why Memory Barriers?中文翻译(上)

转载自:http://www.wowotech.net/kernel_synchronization/Why-Memory-Barriers.html 本文是对perfbook的附录C Why Memory Barrier的翻译,希望通过对大师原文的翻译可以弥补之前译者发布的关于memory barrier的一篇很拙劣的文章的遗憾。本文的翻译不是一一对应的翻译,主要是领会精神,用自己的语言...

厚积薄发者,轻舟万重山 2757

为什么要用Memory barrier

http://scriptogr.am/yifan/post/whymb

babydream520的博客 842
上一篇: linux device 2
下一篇: 从Src rpm 编译 自己修改的内核
tinnycloud
博客等级 码龄21年 9粉丝 17原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值