2021年6月18日

yabo体育靠谱吗-你不想知道CPU如何执行任务吗

你熟悉下面的这几个问题吗?

有内存,为什么需要CPU Cache?

CPU如何读写数据?

如何让CPU更快地读取数据?

CPU医生共享是如何发生的?如何避免呢?

CPU如何安排工作?如果你的任务对回答的要求很高,你希望它总是先被分配,该怎么办?

.

李,我们会回答这些问题。

1605136952938035.png

CPU如何读写数据?首先要了解CPU的体系结构,了解CPU的体系结构,才能更好地理解CPU读写数据的方式。现代CPU的体系结构图如下。

1605136971349302.png

你可以看到,

CPU通常有多个CPU核心,如上图中的1号和2号CPU核心,每个CPU核心都有自己的L1高速缓存。

L2缓存和L1缓存通常分为dCache、iCache和L3缓存

由多个核心共享。这是CPU的典型缓存层。

前面提到的都是CPU内部的Cache。向外看,有内存和硬盘。这个存储设备一起构成金字塔存储层。如下图所示。

1605136987458098.png

如上图所示,从上到下的话,存储容量会变大,访问速度会变慢。对于每个存储设备的访问延迟,请查看下图中的表。

1605137003603865.png

CPU访问LC的速度比访问内存快100倍。所以CPU有L1到L3高速缓存的原因是将高速缓存用作CPU和内存之间的高速缓存层,从而减少对内存的访问频率。

CPU将数据从内存读取到缓存中时,将数据读取为一个片段,而不是一个字节一个字节,这称为CPU线(缓存线)。因此,CPU线是CPU将数据从内存读取到缓存中的单位。

对于CPU Line大小,可以在Linux系统上查看:我可以看到我的服务器的L1缓存线大小是64字节。也就是说,L1缓存一次加载数据的大小为64字节。

1605137020268176.png

然后,阵列的加载,CPU将阵列中连续的多个数据加载到缓存中,因此必须按照物理内存地址的分布顺序访问元素。这样可以在访问数组元素时提高缓存命中率,减少从内存中读取数据的频率,从而提高程序性能。

但是,如果不使用数组而使用单独的变量,则会出现缓存伪共享问题。缓存医生共享问题是性能杀手,应该避免。

接下来,我们来看看缓存医生共享是什么。如何避免这个问题?

现在假设有双核。

CPU,这两个CPU内核并行执行两个不同的线程,同时在内存中运行两个不同的数据:long类型的变量A和

B,这两个数据的地址在物理内存中是连续的。缓存行的大小为64字节,变量A在缓存行中。

因为CPU线是CPU将数据从内存读取到Cache,所以这两个数据在同一个Cache线中

单位,所以这两个数据同时读取到两个CPU内核的每个缓存中。

1605137038470286.png

如果这两个不同的核心线程分别修改不同的数据,例如,1号CPU核心线程只修改变量A,或者2号CPU核心线程的线程只修改变量B,会发生什么情况?

分析伪共享问题现在结合确保多核缓存一致性的MESI协议来说明整个过程。如果还不知道MESI协议,可以看到句子“打开CPU缓存一致性门的10幅图”。

第一个变量A和B还不在缓存中。假设1号核心绑定线程A,2号核心绑定线程B,线程A只读取变量A,线程B只读取和写入变量B。

1605137079154732.png

1

数字核心读取变量A。CPU将数据从内存读取到缓存中的单位是缓存行,确切地说是变量A和变量B

中的数据属于同一个缓存行,因此A和B的数据都加载到缓存中,缓存行显示为独占状态。

1605137096189261.png

接着2号核心开始从存储器中读取变量B。同样,缓存线大小的数据被读取到缓存中。该缓存行的数据中也包含变量A和变量B。这时,1号和2号核心的缓存线状态变为“共享”状态。

1605137112821253.png

1

号码核心需要修改变量A,但发现该缓存线的状态为“共享”状态,因此必须先通过总线向2号核心发送消息,通知2号核心。

Cache的相应Cache Line标记为“已过期”状态,然后是与核心1对应的Cache Line

状态将更改为“已修改”状态,变量A将被修改。

1605137129957229.png

以后,2

号码核心需要修改变量B,在这种情况下,2号核心的缓存中相应的缓存行无效,另外,因为1号核心的缓存。

还有这样的数据,状态为“已修改”,因此必须先将core 1的缓存对应的缓存行写回内存,然后再写2。

数字核心将高速缓存线大小的数据从内存读取到高速缓存中,最后将变量B修改为核心2的高速缓存

将状态标记为“已修改”。

1605137149888980.png

所以,如果

1号和2号CPU核心如此持续地交替修改变量A和B,、重复这两个步骤,缓存

没有缓存效果。因为变量A和B之间没有任何关系,但同时属于缓存行、缓存行。

中的所有数据修改后,会相互影响,出现和这两个阶段。

因此,当多个线程同时读取和写入同一高速缓存行的其他变量时,导致CPU高速缓存失败的这种现象称为伪共享。

由于防止医生共享的方法,在多个线程共享的热点数据(即经常修改的数据)中,应避免在同一个缓存行上。否则,可能会出现医生共享问题。

接下来,让我们看看在实际项目中使用什么方法来避免医生共享问题。

__cacheline_aligned_in_smp宏定义在Linux内核中,用于解决伪共享问题。

1605137215537537.png

在上面的宏定义中,您可以看到:

在多核(MP)系统上,宏定义是__Cache Line_aligned,即缓存行的大小。

在单核系统中,宏定义为空。

因此,对于在同一个缓存行内共享的数据,如果多核之间的竞争很大,则可以使用上面的宏定义对缓存行上的变量进行排序,以避免伪共享。

例如,它具有以下结构:

13.png

结构的两个成员变量A和B在物理内存地址上是连续的,因此可以在同一个缓存行上,如下图所示。

14.png

因此,为了避免上述缓存伪共享问题,可以使用上述宏定义将B的地址设置为缓存行排序地址,如下所示:

15.png

这样,A和B变量就不在同一个缓存行上,如下图所示。

16.png

因此,避免共享缓存医生实际上是使用空间来改变时间的想法,浪费一些缓存空间来提高性能。

让我们来看看另一个应用程序层面的回避场景。Java并发框架Disruptor使用“字节填充继承”来避免伪共享问题。

Disruptor的RingBuffer类经常用于多个线程,如以下代码所示:

1605137267703284.png

您可能会认为RingBufferPad类中的7个long类型的名字很奇怪,但实际上看起来没有什么用,但对提高性能起着非常重要的作用。

CPU高速缓存从内存中读取数据的单位是CPU线,常规64位CPU Cache线大小为64字节,long类型的数据为8字节,因此CPU加载8个long类型的数据。

根据

JVM对象包含RingBufferPad的7个长类型数据

Cache Line填充在前面,RingBuffer的7个long类型的数据填充在Cache Line后面(14个)

Long变量没有实际用途,也不进行读写操作。

1605137283856211.png

另外,RingBufferFelds

里面定义的变量都用final修饰。也就是说,第一次加载后不再修改,“前后”分别填充了7个不可读/写的long

类型变量,所以不管怎么加载Cache Line,整个Cache Line

因为没有可以更新的数据,所以如果经常读取数据,就没有可能被缓存替换,因此不会出现伪共享问题。

CPU如何选择线程?让我们再次了解一下CPU读取数据的过程,然后选择CPU当前要运行的线程。

在中

在Linux内核中,进程和线程以tark_struct结构表示,线程的tark_struct除外

结构中的一些资源共享进程创建的资源,如内存地址空间、片段、文件描述符等,因此Linux中的线程也称为轻量级进程。

Tark_struct的托管资源少于进程的tark_struct,因此使用名称“lights”。

通常,没有创建线程的过程只有一个执行流,称为主线程。如果想让进程处理更多的事情,可以创建多个线程并单独处理,但无论如何,内核对应的都是tark_struct。

1605137299134939.png

因此,Linux内核的调度程序以tark_struct为目标。接下来,这个数据结构统称为任务。

在Linux系统中,根据作业的优先级和响应要求,主要分为两种,优先级值越小,优先级越高。

实时操作,对系统的响应时间要求较高。也就是说,必须尽快执行实时任务。即使优先级在0到99范围内,也可以执行实时操作。

一般作业,响应时间要求不高。优先顺序是100~139范围内的一般工作水平。

调度类对作业具有优先级,因此Linux系统被分为这些调度类,以便尽快运行优先级高的作业,如下图所示。

1605137315990902.png

Deadline和Realtime这两个调度类都适用于实时任务,这两个调度类的调度策略都有三个,其工作方式如下:

SCHED_DEADLINE:根据DEADLINE安排。最接近目前时间的deadline的工作将首先排定。

SCHED_FIFO:对于优先级相同的任务,遵循先到先得的原则,但优先级较高的任务可以抢占优先级较低的任务。也就是说,优先级高的任务可以“插队”。

SCHED_RR:对于优先级相同的作业,它们轮流运行,每个作业都有一定的时间片,时间片用完的作业放置在队列的末尾,以确保相同优先级作业的公平性,但优先级较高的作业可以抢占优先级较低的作业。

Fair调度类适用于一般任务,由CFS调度程序管理,分为两个调度策略。

SCHED_NORMAL:一般作业使用的排程策略

SCHED_BATCH:不与终端交互的后台任务的调度策略,可以在不影响需要交互的其他任务的情况下适当降低优先级。

完全公平地说,我们平日见面的基本都是一般任务,一般任务最重要的是公平性,Linux实现了基于CFS的调度算法,即完全公平的调度(Completely Fair Scheduling)。

此算法的概念是为每个任务保留虚拟运行时vruntime,使分配给每个任务的CPU时间相同。如果作业正在运行,则运行时间越长,该作业的vruntime自然越大,如果没有正在运行的作业,则vruntime不会更改。

然后,在调度CFS算法时,优先选择vruntime较少的任务,以确保每个任务的公平性。

这就像让你把一桶奶茶平均分成10杯奶茶杯一样。看到哪个奶茶少,就倒更多。哪一个多,先不倒。这样多次操作,不能保证每杯奶茶的确切数量,但至少是公平的。

当然,上述例子没有考虑到优先问题。虽然是一般工作,但是一般工作之间有优先顺序区分,所以正在计算虚拟执行时间。

Vruntime还会考虑一般作业的权重值。权重值不是优先级值。内核有nice,它是nice级别和权重值的转换表

级别越低,权重值越大。后面会说明nice值是什么。

所以有以下公式。

21.png

你不用管

什么是NICE_0_LOAD,你认为它是常数。那么,在“相同的实际运行时”中,权重较高的作业的运行时比权重较低的作业低

Vruntime很少。你可能会好奇为什么很少。还记得CFS日程吗?您将优先选择vruntime。

由于调度较少的任务,因此优先分配权重较高的任务,因此权重较高的实际运行时间自然会增加。(大卫亚设,北方执行部队)。

CPU执行队列一个系统通常会运行很多任务,多任务处理的数量基本上远远超过CPU核心的数量,因此需要排队。

事实上,每一个

CPU有自己的执行队列(Run Queue,rq),该队列描述了在此CPU上运行的所有进程,该队列包含三个执行队列:Deadline

运行队列dl_rq、实时作业运行队列rt_rq和CFS运行队列csf_rq,其中csf_rq以红色黑色树表示

对齐Vruntime大小,最左侧的叶节点是下一个计划任务。

1605137375366501.png

这些调度类具有优先级,优先级如下:Deadline

real time Fair(Linux)在选择运行下一个作业时,按此优先级顺序选择

在Dl_rq中选择作业,在rt_rq中选择作业,然后在csf_rq中选择作业。因此,实时任务总是比常规任务优先运行。

启动优先级协调任务时,除非特别指定优先级,否则默认情况下为常规任务,常规任务的调度类为Fail,并由CFS调度程序管理。CFS调度程序的目的是实现任务执行的公平性。也就是说,保证每个任务的执行时间。

要为常规任务指定更多的运行时间,可以调整任务的nice值,使优先级高的任务执行更多的时间。可以设置Nice值的范围为-20到19,值越低,优先级越高,因此-20是最高优先级,19是最低优先级,默认优先级为0。

你这样认为吗?

Nice值的范围很奇怪吗?事实上,尼克

值不是表示优先级,而是表示优先级的修改值。与优先级的关系是priority(new)=

Priority(old) nice。在内核中,priority的范围为0~139,值越低,优先级越高,其中0~99排在前面

范围用于实时操作,nice值映射到100到139。此范围提供给一般作业,因此nice值调整一般作业的优先级。

23.png

如上所述,权重值和nice值之间的关系是,nice值越低,权重值越大,计算的vruntime越少。计划CFS算法时,优先选择要运行的vruntime较少的作业,因此nice值越低,作业优先级越高。

启动任务时,您可以指定nice的值。例如,可以将mysqld指定为-3优先级。

24.png

要修改已运行任务的优先级,可以使用renice调整nice值。

25.png

Nice会调整一般作业的优先顺序,因此无论如何缩小nice值,作业始终是一般作业。如果某些任务的实时要求比较高,则可以更改任务的优先级并计划策略,使其成为以下实时任务:

26.png

了解CPU如何读写数据的前提是了解CPU的体系结构。CPU内部多个缓存外部的内存和磁盘构成了金字塔的内存结构。从这个金字塔往下走,存储容量会变大,但访问速度会变小。

CPU读写数据时,不是以1字节为单位读写,而是以CPU线大小为单位,CPU线大小通常为64字节。也就是说,CPU在读取和写入数据时,每次都以64字节的大小工作。

因此,如果要操作的数据是数组,则在访问数组元素时,将根据内存分布的地址顺序访问,从而最大限度地利用。

缓存,程序性能提高了。但是,操作的数据不是数组,而是通用变量,多核CPU需要避免缓存行。

医生共享的问题。

所谓缓存行伪共享问题是在多个线程同时读取和写入同一缓存行的不同变量时发生的

CPU缓存失败现象。那么,对于多个线程共享的热点数据(即经常修改的数据),应避免在同一个缓存行上

中避免的方法通常包括缓存行大小字节对齐、字节填充等。

系统需要运行的多线程数量通常大于CPU内核,允许线程等待CPU。这可能会导致延迟,如果延迟容差低,则可以通过一些人为手段干预Linux的默认调度政策和优先级。

yabo体育靠谱吗-你不想知道CPU如何执行任务吗

发表评论

电子邮件地址不会被公开。 必填项已用*标注