多处理器多核环境对软件设计决策的影响

2019-07-17  本文已影响0人  王侦

1.在芯片多线程、多核、多处理器架构上软件可伸缩性的阻碍

2.芯片多处理器、多核、多线程系统

所有核和处理器都共享系统总线,并通过系统总线访问主要内存或 RAM。对于应用程序和操作系统,该系统看起来就像是 8 个逻辑处理器。

2.1 缓存一致性

缓存一致性是表示处理器缓存中的数据项目值与系统内存中的数据项目值相同的一种状态。该状态对于软件来说是透明的。但是,系统为了保持缓存一致性而执行的操作可能会影响软件的性能。

如果这两个线程都正在读取和写入相同的数据项,那么该系统必须执行额外的操作,以确保在进行每个读取和写入操作时,线程都看到相同的数据值。如果每个线程都进行了一系列的写入操作,那么这可能会严重影响系统的性能,因为所有时间都花费在等待更新系统内存中的数据值上。这种情况被称为“乒乓效应”,当在多处理器和多核系统上运行时,应避免发生这种情况,这是一项重要的软件设计注意事项。

2.2 嗅探 - Snooping

这是一个跟踪每个缓存行的状态的缓存子系统。该系统使用一个称为 “总线动态监视” 或者称为“总线嗅探” 的技术来监视在系统总线上发生的所有事务,以检测缓存中的某个地址上何时发生了读取或写入操作。

缓存行状态MESI:

缓存行状态MESI:

3.多处理器多核环境对软件设计决策的影响

3.1 避免内存争用

在内存和缓存中,各种不同的核共享一个通用的数据区域,这需要在它们之间进行同步。当不同的核同时访问同一个数据区域时,会发生内存争用。在不同的核之间同步数据会因总线通信、锁定成本以及缓存缺失而有很大的性能损失。

如果应用程序有多个线程,并且所有线程都更新或修改同一个内存地址,那么正如前面部分所讨论的那样,为了保持缓存一致性,可能会产生一次重大的乒乓效应。这会导致性能降低。

避免内存争用的技巧:

3.2 避免伪共享

如果两个或多个处理器正在向同一缓存行的不同部分中写入数据,那么很多缓存和总线通信可能会导致其他处理器上的旧行的每个缓存副本失效或进行更新。这称为 “伪共享” 或者也称为 “CPU 缓存行干扰”。

和两个或多个线程共享同一数据(因此需要程序化的同步机制来确保按顺序访问)的真正共享不同,当两个或多个线程访问位于同一缓存行上的无关数据时,就会产生伪共享。



即使线程正在修改内存中的不同区域(红色和蓝色箭头),为了保持缓存一致性,在加载该缓存行的所有处理器上,该缓存行会失效,从而强制进行更新。

避免伪共享的技巧:

处理伪共享的两种方式:

在Oracle JDK 8 / OpenJDK 8里有一个新功能,叫做 @Contended ,可以用来减少false sharing的情况。本质上来说就是用户在源码上使用@Contended注解来标注哪些字段要单独处理,避免与其它字段放得太近导致false sharing,然后JVM的实现在计算对象布局的时候就会自动把那些字段拿出来并且插入合适的大小padding。

它的JEP在这里:JEP 142: Reduce Cache Contention on Specified Fields
Aleksey Shipilёv大大写过几篇文章都提到了它,这里有其中一篇:What Heap Dumps Are Lying To You About

要在用户代码(非bootstrap class loader或extension class loader所加载的类)中使用@Contended注解的话,需要使用 -XX:-RestrictContended 参数。

3.3 消除或减少锁争用

软件设计者的主要目标应该是消除共享,以便线程或进程之间不会发生资源争用。

避免锁争用的技巧:

减少锁争用的技巧:

3.4 避免堆争用

是全局资源,在某个进程中的线程之间共享并争用。堆争用是内存密集型多线程应用程序的瓶颈之一。

避免堆争用的技巧:

3.5 提高处理器关联

处理器关联是一个线程或进程属性,该属性告诉操作系统可以在哪些核或逻辑处理器上运行进程。这更适合嵌入式软件设计。

在该配置中,在同一处理器上的两个核之间以及不同处理器上的两个核之间,缓存子系统的行为将会有所不同。如果两个相关的进程或某个进程的两个相关线程被分配给同一处理器上的两个核,那么它们可以更好地利用共享的 L2 缓存,而且可以减少保持缓存一致性的开销。


4.编程模型

当为应用程序中的线程分配工作时,软件设计者可以考虑两个不同的编程模型。

参考

上一篇下一篇

猜你喜欢

热点阅读