dmb ish osh

news2024/7/6 18:40:24

转自:原理和实战解析Linux中如何正确地使用内存屏障

圈里流传着一句话“珍爱生命,远离屏障”,这足以说明内存屏障是一个相当晦涩和难以准确把握的东西。使用过弱的屏障,会导致软件不稳定。使用过强的屏障,会引起性能问题。所以工程上,追求恰到好处、不偏不倚的屏障。本文力求用最浅显的语言,讲清楚内存屏障最晦涩的道理,本文也会给出五个工程案例,这些案例皆见于开源的代码,不涉及任何组织和个人未公开的技术。

一、引子

我国古代著名程序猿韩愈曾经写下一个名为《春雪》的函数:

新年都未有芳华,二月初惊见草芽。

白雪却嫌春色晚,故穿庭树作飞花。

这段代码讲述了一个关于memory reorder的故事,在计算机世界里面,冬天和春天并没有明确的界限,明明已经是春天了,但是还飘着冬天的雪。

下面我们看另外一段程序:

我们能确保c = 4吗?实际上,任何一个角度都确定不了。比如CPU0上面a = 3是“下雪”,flag = 1是“春天”,a=3看似在flag=1之前,实际可能由于memory reorder的原因发生在flag = 1之后,所以flag即便已经等于1,a也不一定等于3。

我们再退一万步讲,哪怕CPU0上面确实确保了春天不下雪,flag=1的时候a 100%就等于了3,那CPU1那边就万无一失了吗?答案也是否定的,因为,在CPU1上面,即便我们的代码是if(flag==1),接下来才做c=a+1,我们也不能确保a的load一定发生在flag==1之后。别忘了,CPU1会投机执行,比如碰到if(flag==1)这种条件,CPU可能直接忽略,不管三七二十一,还是可能先执行 load a, a+1的动作,然后反过来发现flag等于1,然后我认为我的投机是成功的;即便投机失败,CPU只需要保证load a, a+1的这些指令不retired就好。所以CPU1的load a, a+1完全可能发生在flag确切地等于1之前,因此即便CPU0保序了,CPU1仍然不能确保c=4。

我们看看CPU1在投机成功时候的行为逻辑和思想情感:

1. flag==1吗?

2. 不知道啊!我现在还没读出flag呢!

3. 管它呢,先假装flag==1吧,投机一把,执行load a, 把a+1看看

4.flag==1吗?哇,它真地等于1,太爽了,load a和a+1已经做完了。

如果投机失败了呢?

1. flag==1吗?

2. 不知道啊!我现在还没读出flag呢!

3. 管它呢,先假装flag==1吧,投机一把,执行load a, 把a+1看看

4. flag==1吗?Oh,shit,它不等于1,load a, a+1白做了.....

这就是弱序系统的典型特点。请问CPU为什么要这么“混乱”?这正是现代CPU为了保证高效执行厉害的地方,但是也引入软件使用上的复杂度。这种复杂度,类似于宋代著名程序媛李清照的函数《声声慢·寻寻觅觅》:“寻寻觅觅,冷冷清清,凄凄惨惨戚戚。乍暖还寒时候,最难将息。三杯两盏淡酒,怎敌他、晚来风急?雁过也,正伤心,却是旧时相识。满地黄花堆积,憔悴损,如今有谁堪摘?守着窗儿,独自怎生得黑。梧桐更兼细雨,到黄昏、点点滴滴。这次第,怎一个愁字了得!”请问李清照童鞋说的究竟是春天还是秋天还是春天呢?据说至今也没有人能够解密。仅凭“乍暖还寒”一定会觉得是初春,但是你再继续看到“雁过也”、“满地黄花堆积”,这显然又不是春天的景象。

罢了罢了,这一切都不重要了,重要的是,四季并不分明,四季没有明确的界限。这是我们要牢记的第一个point!

二、屏障

正是因为四季没有明确的界限,所以当我们希望看到明确的顺序的时候,我们希望引入一道屏障。让冬天跑不到春天,让春天跑不过去冬天。

典型的ARM64有这么几种屏障:

a. DMB:Data Memory Barrier

b. DSB:Data Synchronization Barrier

c. ISB:Instruction Synchronization Barrier

d. LDAR(Load-Acquire)/STLR(Store-Release)

我们随便打开ARM的手册,看一个DMB的定义:

The Data Memory Barrier (DMB) prevents the reordering of specified explicit data accesses across the barrier instruction. All explicit data load or store instructions, which are executed by the PE in program order before the DMB, are observed by all Observers within a specified Shareability domain before the data accesses after the DMB in program order.

码农的内心是崩溃的,人生已经这么悲催了,你为什么还要拿这样的绕口令来折磨我?什么叫“are observed by all Observers”?

下面我们给大家讲述2只狗狗出家门的故事:

上图的2只狗,首先在一个inner shareable domain里面,比如是自己的家门里面;然后是在一个outer shareable domain里面,比如是小区的出口;最后在太阳系里面。这2只小狗,出每一道门,都有observer可以看见它,有的observer是inner的(observer1),有的observer是outer的(observer2),有的observer属于full system,比如天上的嫦娥(observer3)。

现在我们提出如下需求:

a. 黄狗狗出门后白狗狗出门。

b. 黄狗狗和白狗狗出门后,放烟雾消杀。

当我们提出这样的需求的时候,我们看3样东西:

1. 我们首先要看需要保证顺序的2个事物的特征

在需求1里面,是2只特征一样的东西,都是狗狗;在需求2里面,两个事物之间一个是狗狗,一个是消杀的烟雾,显然不是同类。

狗狗在硬件和Linux软件层面上,可以理解为针对内存的memory load/store指令;放烟雾,这种不属于memory的load/store,比如你执行的是tlbi、add加法或者写的是ARM64系统寄存器(MSR指令),则显然不属于memory load/store。

这里就涉及到DMB和DSB的一个本质区别,DMB针对的是memory的load/store之间;DSB强调的是同类或不同类事物的先后完成。

所以对于这个场景,我们正确的屏障是:

load黄狗狗

dmb ??

load白狗狗

dsb ??

MSR 消毒烟雾

第一个是dmb,第2个是dsb。上面dmb和dsb后面都加了两个“?”,证明这里有情况,什么情况?接着看。

2. 其次我们要看保序的observer在哪里

比如是家门口的小姑娘observer1(ISH,inner shareable)、还是小区门口的小姑娘observer2(OSH,outer shareable),还是天上的嫦娥呢(SY, Full System)?如果只是observer1看到黄狗狗先出门,白狗狗再出门,延迟显然更小。在越大的访问范围保序,硬件的延迟越大。假设我们现在的保序需求是:

a. 小区门口(outer shareable)的observer2先看到黄狗狗出来,再看到白狗狗出来;

b. 家门口(inner shareable)的observer1先看到两只狗狗出来,再看到放烟雾。

对于这个场景,我们正确的屏障是:

load黄狗狗

dmb OSH?

load白狗狗

dsb ISH?

MSR 消毒烟雾

在DMB后面我们跟的是OSH,在DSB后面我们跟的是ISH,是因为observer的位置不一样。注意,能用小observer的不用大observer。小区门口的observer,没有透视眼+望远镜,是看不到你家门口的狗狗的。

在一个典型的ARM64系统里面,运行Linux的各个CPU在一个inner;而GPU,DMA和CPU则同位于一个outer;当然还有可能孤悬海外的一个Cortex-M3的MCU,尽管可以和CPU以某种方式通信,但是不太参与inner以及outer里面的总线interconnect。

3. 最后我们保序的方向是什么

前面我们只关心狗狗的出门(load),假设两只狗狗都是进门(store)呢?或者我们现在要求黄狗狗先进门,白狗狗再出门呢?这个时候,我们要约束屏障的方向。

比如下面的代码,约束了observer1(inner)先看到黄狗狗出门,再看到白狗狗出门:

load黄狗狗

dmb ISHLD

load白狗狗

比如下面的代码,约束了observer2(outer)先看到黄狗狗进门,再看到白狗狗进门:

store黄狗狗

dmb OSHST

store白狗狗

这里我们看到一个用的是LD,一个用的是ST。我们再来看几个栗子,它们都是干什么的:

a. A(load); dmb ISHLD; B; C(load/store)

保证Inner内,A和C的顺序,只要A是load,无论C是load还是store;如果B既不是load也不是store,而是别的性质的事情,则dmb完全管不到B;

b. A(load); dsb ISHLD; B; C(load/store)

保证Inner内,A和C的顺序,只要A是load,无论C是load还是store;无论B是什么事情,inner都先到干完了A,再干B(注意这里是dsb啊,亲)。

c. A(store); dmb ISHLD; B; C(store)

A,B,C三个东西完全乱序,因为dmb约束不了性质不同的B,“LD”约束不了A和C的store顺序。

d. A(store); dmb ISHST; B; C(store)

ST约束了A和C 2个东西在inner这里看起来是顺序的,因为dmb约束不了B,所以B和A、C之间乱序。

注意上述4个屏障,由于都是ISH,故都不能保证observer2和observer3的顺序,在observer2和3眼里,上述所有屏障,A、B、C都是乱序的。

另外,如果无论什么方向,我们都要保序,我们可以去掉LD和ST,这样的保序方向是any-any。

到这里我们要牢记3个point:谁和谁保序;在哪里保序;朝哪个方向保序。

由此,我们可以清楚地看到DMB和DSB的区别,一个是保序内存load,store;一个是保序内存load,store + 其他指令。ISB的性质会有很大的不同,ISB主要用于刷新处理器中的pipeline,因此可确保在 ISB 指令完成后,才从内存系统中fetch位于该指令后的其他指令。比如你更新了代码段的PTE,需要重新取指。而LDAR(Load-Acquire)/STLR(Store-Release)则是比较新的one-way barrier。如下图,LDAR之前的LDR、STR可以跑到LDAR之后,但是不能跑到STLR之后;STLR之后的LDR,STR可以跑到STLR之前,但是不能跑到LDAR之前。所以STLR堵住了前面的往后面跑,LDAR堵住了后面的往前面跑。下面夹在LDAR和STLR之间的LDR,STR由于两边都是单向车道,而且都与它的行进方向相反,所以它夹在死胡同里,哪里也去不了。

注意,LDAR和STLR与前面的dmb, dsb有本质的不同,它本身是要跟地址的。比如现在家里有3只狗狗:

假设我们现在的要求是黄狗狗一定要在红尾哈巴狗之后出门,而白狗狗什么时候出我们都不在乎,则代码逻辑为:

ldr 白狗狗

ldar 红尾哈巴狗

ldr 黄狗狗

黄狗狗被红尾哈巴狗的ldar挡住了,而白狗狗没有被任何东西挡住,它可以:

1. 第一个出门

2. 红尾哈巴狗出门后,黄狗狗出门前出门

3. 最后一个出门。

三、API

在Linux内核,有4组经典API:

SMP屏障

此屏障主要用于运行Linux的多个核之间对内存访问的保序,所以它主要是dmb,它是ish,通过ld、st来区分保序的方向。

DMA屏障

此屏障主要用于运行Linux的多个核与DMA引擎之间的保序,所以它主要是dmb,它是osh,通过ld、st来区分保序的方向。

屏障

非常严格的完成屏障,mb()保证了前面的指令的完成,前面的指令不必是load,store,比如可以是TLBI。dsb(ld)、dsb(st)则要弱一点,分别保证前面的load,store执行完了才执行后面的指令。

load_acquire/store_release

逻辑通常是一种成对的__smp_load_acquire()、__smp_store_release()逻辑,特别适合2个或者多个CPU之间的链式保序。在ARM64里面用的是stlr,ldar实现如下:

比如,下面的代码逻辑,保证了CPU0、CPU1、CPU2这3个CPU在链条上保序访问:

中间循环了一个链条逻辑,从而保证了这三个CPU中间内存访问的一些保序:

下面我们进入五个工程实战,“熟读唐诗三百首,不会吟诗也会吟”,最后我们会形成针对内存屏障正确用法的语感,而全然忘记语法。

实战一:运行Linux的多核通过中断通信

它的一般模式是:CPU0在DDR填入一段数据,然后通过store指令写INTR的寄存器向CPU1发送中断。

store数据

barrier?

store intr寄存器

中间应该用什么barrier?我们来回忆一下三要素:

a. 谁和谁保序? -> CPU0和CPU1这2个observer之间看到保序

b. 在哪里保序? -> 只需要CPU1看到CPU0写入DDR和intr寄存器是保序的

c. 朝哪个方向保序? -> CPU0写入一段数据,然后写入intr寄存器,只需要在st方向保序。

由此,我们得出结论,应该使用的barrier是:dmb + ish + st,显然就是smp_wmb。内核代码drivers/irqchip/irq-bcm2836.c也可以证实这一点:

里面的注释非常清晰,smp_wmb()保证了发起IPI之前,其他CPU应该先观察到内存的数据在位。

现在我们把INTR换成gic-v3,就会变地tricky很多。gic-v3的IPI寄存器并不是映射到内存空间的,而是一个sys寄存器,通过MSR来写入。前面我们说过DMB只能搞定load/store之间,搞不定load/store与其他东西之间。

最开始的gic-v3驱动的作者其实也误用了smp_wmb,造成了该驱动的稳定性问题。于是Shanker Donthineni童鞋进行了一个修复,这个修复的commit如下:

这个commit解释了我们不能用dmb搞定memory和sysreg之间的事情,于是这个patch替换为了更强力的wmb(),那么这个替换是正确的吗?

我们还是套一下三要素:

a. 谁和谁保序? -> CPU0和CPU1保序

b. 在哪里保序? -> 只需要CPU1看到CPU0写入DDR后,再看到它写sysreg

c. 朝哪个方向保序? -> CPU0写入一段数据,然后写入sysreg寄存器,只需要在st方向保序。

我们要进行保序的是CPU0和CPU1之间,显然他们属于inner。于是,我们得出正确的barrier应该是:dsb + ish + st,wmb()属于用力过猛了,因为wmb = dsb(st),保序范围是full system。基于此,笔者再次在主线内核对Shanker Donthineni童鞋的“修复”进行了“修复”,缩小屏障的范围,提升性能:

实战二:写入数据到内存后,发起DMA

下面我们把需求变更为,CPU写入一段数据后,写Ethernet控制器与CPU之间的doorbell,发起DMA操作发包。

我们还是套一下三要素:

a. 谁和谁保序? -> CPU和EMAC的DMA保序,DMA和CPU显然不是inner

b. 在哪里保序? -> 只需要EMAC的DMA看到CPU写入发包数据后,再看到它写doorbell

c. 朝哪个方向保序? -> CPU写入一段数据,然后写入doorbell,只需要在st方向保序。

于是,我们得出正确的barrier应该是:dmb + osh + st,为什么是dmb呢,因为doorbell也是store写的。我们来看看Yunsheng Lin童鞋的这个commit,它把用力过猛的wmb(),替换成了用writel()来写doorbell:

在ARM64平台下,writel内嵌了一个dmb + osh + st,这个从代码里面可以看出来:

同样的逻辑也可能发生在CPU与其他outer组件之间,比如CPU与ARM64的SMMU:

实战三:CPU与MCU通过共享内存和hwspinlock通信

下面我们把场景变更为主CPU和另外一个cortex-m的MCU通过一片共享内存通信,对这片共享内存的访问透过硬件里面自带的hwspinlock(hardware spinlock)来解决。

我们想象CPU持有了hwspinlock,然后读取对方cortex-m给它写入共享内存的数据,并写入一些数据到共享内存,然后解锁spinlock,通知cortex-m,这个时候cortex-m很快就可以持有锁。

我们还是套一下三要素:

a. 谁和谁保序? -> CPU和Cortex-M保序

b. 在哪里保序? -> CPU读写共享内存后,写入hwspinlock寄存器解锁,需要cortex-m看到同样的顺序

c. 朝哪个方向保序? -> CPU读写数据,然后释放hwspinlock,我们要保证,CPU的写入对cortex-m可见;我们同时要保证,CPU放锁前的共享内存读已经完成,如果我们不能保证解锁之前CPU的读已经完成,cortex-m很可能马上写入新数据,然后CPU读到新的数据。所以这个保序是双向的。

Talk is cheap, show me the code:

里面用的是mb(),这是一个dsb+full system+ld+st,读代码的注释也是一种享受。

实战四:SMMU与CPU通过一个queue通信

现在我们把场景切换为,SMMU与CPU之间,通过一片放入共享内存的queue来通信,比如SMMU要通知CPU一些什么event,它会把event放入queue,放完了SMMU会更新另外一个pointer内存,表示queue增长到哪里了。

然后CPU通过这样的逻辑来工作

这是一种典型的控制依赖,而控制依赖并不能被硬件自动保序,CPU完全可以在if(pointer满足什么条件)满足之前,投机load了queue的内容,从而load到了错误的queue内容。

我们还是套一下三要素:

a.谁和谁保序? -> CPU和SMMU保序

b.在哪里保序? -> 要保证CPU先读取SMMU的pointer后,再读取SMMU写入的queue;

c.朝哪个方向保序? -> CPU读pointer,再读queue内容,在load方向保序

于是,我们得出正确的barrier应该是:dmb + osh + ld,我们来看看wangzhou童鞋的这个修复:

ARM64平台的readl()也内嵌了dmb + osh + ld屏障。显然这个修复的价值是非常大的,这是一个由弱变强的过程。前面我们说过,由强变弱是性能问题,而由弱变强则往往修复的是稳定性问题。也就是这种用错了弱barrier的场景,往往bug非常难再现,需要很长时间的测试才再现一次。

实战五:修改页表PTE后刷新tlb

现在我们的故事演变成了,CPU0修改了页表PTE,然后通知其他所有CPU,PTE应该被更新,其他CPU需要刷新TLB。

它的一般流程是CPU调用set_pte_at()修改了内存里面的PTE,然后进行tlbi等动作。这里就变地非常复杂了:

我们看看barrier1,它在屏障store和tlbi之间,由于二者一个是狗狗,一个是消杀烟雾,显然不能是dmb,只能是dsb;我们需要CPU1看到set_pte_at的动作先于tlbi的动作,所以这个屏障的范围应该是ISH;由于屏障需要保障的是set_pte_at的store,而不是load,所以方向是st,由此我们得出第一个barrier应该是:dsb + ish + st。

详细的流程我们可以参考下如下代码:

barrier2用的是dsb(ish),它保证了inner内的CPU都先看到了tlbi的完成;barrier3用的isb(),它保证了CPU fetch到PTE修正之后的指令。

结语

本文对Linux内核的内存屏障的原理和用法进行一些分析和实战,它并未覆盖内存屏障的全部知识,但是应该可应付工程里面90%以上的迷惘和困惑。由于作者水平有限,文中疏漏与错误在所难免,恳请读者朋友们海涵。本文完成之时,北半球正在告别烈日炙烤的夏季,南半球即将迎来姹紫嫣红的春天,愿所有人都有一个美好的未来。

参考文献

【1】https://developer.arm.com/documentation/100941/0101/Barriers

【2】https://www.kernel.org/doc/Documentation/memory-barriers.txt

【3】https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/

【4】https://developer.arm.com/documentation/den0024/a/Memory-Ordering/Barriers/One-way-barriers

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/78463.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

c语言篇(动态内存管理)

前言: 对于数据的存储我们可以静态存储,也可以动态存储,两种方式都有自己特有的好处,这篇文章教我们如和进行动态的数据存储!!!! 💞 💞 欢迎来到小马学习代码…

自动驾驶之单目3D目标检测TensorRT调研

目前在github上只能找到2个项目 TensorRT-CenterNet-3D tkDNN 两者都是使用CenterNet,但第1个基于TensorRT5,无法与当前最新的TensorRT6和TensorRT7兼容。经测试,第1个无法在XavierJetpack 4.3/4.4上部署,因此选择部署第二个tkDNN。 1. 基本…

Python学习基础笔记三十八——time模块

1、time模块:和时间有关系的,我们就用到了时间模块: import timeprint(time.time()) #获得当前时间戳 2、表示时间的三种方式: 在Python中,通常用三种方式来表示时间:时间戳、元组(struct_time)、格式…

LeetCode刷题复盘笔记—一文搞懂动态规划之337. 打家劫舍 III问题(动态规划系列第十九篇)

今日主要总结一下动态规划完全背包的一道题目,337. 打家劫舍 III 题目:337. 打家劫舍 III Leetcode题目地址 题目描述: 小偷又发现了一个新的可行窃的地区。这个地区只有一个入口,我们称之为 root 。 除了 root 之外&#xff0…

UDS入门至精通系列:Service 19(二)

文章目录 前言一、协议的定义(19 04/06)二、数据库编辑(CANdelaStudio)三、CAPL应用(Service 19 04 / 06)总结前言 本文主要讲述了ECU诊断中用到的DTC Status以及Service 19 02作用和用法。 本文主要讲述了ECU诊断中用到的DTC Status以及Service 19 02作用和用法。 本文…

Transformer15

今天还是Transformer~~ 都连载这么多了 , 据说是全球首个面向遥感任务设计的亿级视觉大模型 大规模视觉基础模型在基于自然图像的视觉任务中取得了重大进展。得益于良好的可扩展性和表征能力,基于视觉Transformer (Vision Transformer, ViT) 的大规模视觉基础模型吸…

Instruction Tuning(FLAN、instructGPT、chatGPT)

首页最近被chatGPT刷屏,但翔二博主左看右看发现很多想法似乎都是一脉相通的,于是连夜从存档中找了一些文章尝试理一理它的理论路线。 具身智能综述和应用(Embodied AI)多模态中的指令控制 同时想到今年在智源人工智能前沿报告&a…

线程,线程池的使用

文章目录线程,线程池的使用1. 多线程基础1.1 线程和进程1.2 多线程的创建1.2.1 继承Thread类1.2.2 实现Runnable接口1.2.3 匿名内部类方式1.2.4 守护线程1.3 线程安全1.3.1 卖票案例1.3.2 线程同步2. 线程池的实现方式2.1 Java提供的四种线程池2.2 线程池的创建原理…

微信小程序开发【从0到1~入门篇】

目录 1. 微信小程序介绍 1.1 什么是小程序? 1.2 小程序可以干什么? 2. 申请账号 2.1 申请帐号 2.2 测试号申请(我们小程序账号申请完成之后,建议务必要申请一个测试号用来开发) 3. 安装开发工具 3.1 选择稳定…

我的创作纪念日(2021-12-10 2022-12-10)

🌆 内容速览阴差阳错成为一名博主?这一年来的收获日常生活未来憧憬阴差阳错成为一名博主? 如上图所见,她就是我在CSDN上发布的第一篇博客——无标题,有时候机缘来的那么突然,我甚至都没有给她想一个凑合的名…

spring——Spring 注入内部Bean——构造函数方式注入内部 Bean

项目依赖&#xff1a; <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0"xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation"http://maven.apache.or…

【MySQL进阶篇】存储引擎

&#x1f349;个人主页&#xff1a;个人主页 &#x1f353;系列专栏&#xff1a;MySQL数据库 目录 1.MySQL体系结构 1). 连接层 2). 服务层 3). 引擎层 4). 存储层 2.存储引擎介绍 3.存储引擎特点 1. InnoDB 2.MyISAM 3.Memory 4.区别及特点 4.存储引擎选择 1.MySQ…

MAC QT OpenGL 图像曝光度调节

目录 一.MAC QT OpenGL 图像曝光度调节演示 1.原始图片2.效果演示 二.MAC QT OpenGL 图像曝光度调节源码下载三.其他平台图像曝光度调节版本 1.IOS 曝光度演示效果2.Windows OpenGL ES 曝光度演示效果3.Windows OpenGL 曝光度演示效果 四.猜你喜欢 零基础 OpenGL ES 学习路线推…

39-kafka-监控Eagle

39-kafka-监控Eagle&#xff1a; Eagle的安装 1.修改 kafka 启动命令 修改 kafka-server-start.sh 命令中 if [ "x$KAFKA_HEAP_OPTS" "x" ]; then export KAFKA_HEAP_OPTS"-Xmx1G -Xms1G" fi 为 if [ "x$KAFKA_HEAP_OPTS" &qu…

功能测试(八)—— APP之专项测试、性能测试、性能测试工具SoloPi

目录 APP测试要点 目标 一、APP专项测试 1.1 兼容性 1.2 安装 1.3 卸载 1.4 升级 1.5 干扰测试(交叉事件测试) 1.6 Push推送 1.7 用户体验 二、 性能测试工具 2.1 APP性能测试工具介绍 —— SoloPi简介 2.2 APP性能测试工具 —— SoloPi使用 三、APP性能测试 3.…

English Learning - L2 窥得大段表达门径 2022.12.8 周四

English Learning - L2 窥得大段表达门径 2022.12.8 周四引言2 形容词2.1 -ing 形容词 VS -ed 形容词核心思想举例3 名词3.1 修饰成分修饰成分的排列的黄金原则&#xff1a;左二右六举例3.2 名词的数3.2.1 "名词 介词/副词/不定式 等" 构成的复合名词变复数&#xf…

Spring Boot 使用 Micrometer 集成 Prometheus 监控 Java 应用性能

一、背景 SpringBoot的应用监控方案比较多,SpringBoot + Prometheus + Grafana是目前比较常用的方案之一。它们三者之间的关系大概如下图: 二、Micrometer的介绍 Micrometer为Java 平台上的性能数据收集提供了一个通用的 API,它提供了多种度量指标类型(Timers、Guauges、…

【强化学习论文合集】十三.2018机器人与自动化国际会议论文(ICRA2018)

强化学习(Reinforcement Learning, RL),又称再励学习、评价学习或增强学习,是机器学习的范式和方法论之一,用于描述和解决智能体(agent)在与环境的交互过程中通过学习策略以达成回报最大化或实现特定目标的问题。 本专栏整理了近几年国际顶级会议中,涉及强化学习(Rein…

超详细的pytest教程(二)之前后置方法和fixture机制

前言 上一篇文章入门篇咱们介绍了pytest的基本使用&#xff0c;这一篇文章专门给大家讲解pytest中关于用例执行的前后置步骤处理,pytest中用例执行的前后置处理既可以通过测试夹具(fixtrue)来实现&#xff0c;也可以通过xunit 风格的前后置方法来实现。接下来我们一起看看如何…

Unity - 技术美术

198.Shader Graph 旗帜飘扬 官方教程链接&#xff1a;https://learn.unity.com/project/make-a-flag-move-with-shadergraph 本节课程文档&#xff1a;https://gitee.com/chutianshu1981/AwesomeUnityTutorial/blob/main/%E5%9B%BE%E5%BD%A2-%E6%8A%80%E6%9C%AF%E7%BE%8E%E5%B…