多核编程杂谈

多核计算杂谈--讨论在多核编程时。在CPU和内存层次上应该知道的一些东西。

尝试找到协调多核工作的本质上的问题。

这里讨论基本上參考x86体系,然后依据须要简化或改动。

先看看各个缓存:

为了解决訪问存储器和CPU操作之间的不平衡,使得存储器訪问不拖后退,利用局部性原理,将存储器分级,提升存储器读写性能的方案,称之为缓存。在这里的思考中。先把各个缓存去掉。于是面对的就是若干核。同一个存储器。这样看比較简单。所谓存储系统就变成黑盒,缓存通过自己的协议,保证不会读到脏数据,保证写的有效。

(可是实际上在优化中,能立竿见影的方向是缓存,这里将存储视为黑盒不表示缓存不重要)。

x86在读写某些长度的数据,且数据位置满足一定的对齐条件时,由于使用总线资源的竞争关系,使得这些操作被按一定顺序运行,同一时候也称作为些操作是原子的。为什么某些操作不是原子的呢?由于这些操作须要读。计算,写等,导致多次訪问存储器,而在不同的訪问之间,可能有其他操作,这时称操作不是原子的。

站在存储器角度来看,接受的事实上上是一个个已经排序好的读或写操作。而作为存储器。须要提供的保证就是在写了某个位置之后,后面的读这个位置要保证输出是最后一次写的数据。

可是,操作是由总线来控制的,假设有多条总线呢?存储器也应该保证同一个位置上读的是最后一次写的数据。所以在设计存储器时,应该考虑怎样保证上述描写叙述的正确性。

所以就有了X读Y写存储器:在X读Y写的情况下,对于同一个基本存储单元的操作间是相互排斥的。

在这里我们对存储器的一些考虑,得到的一个抽象存储器。实际就是一个最简单的并发对象。每一个存储单元就是一个并发对象,称之为原子寄存器。

该并发对象可能被多个核读写。可是本身会保证相互排斥读写。

更上一层,将这些存储单元组织起来,作为存储器这个总体。也是一个并发对象。每一个存储单元的正确性,能否保证整个存储器的正确性?

这里引入了一个概念。并发对象:提供一些操作。并且能够供多个核同一时候使用操作。并发对象本身依据自己的抽象,要在多个操作同一时候进行的情况下保证某种正确性。这种正确性有:静态一致性、顺序一致性、可线性化。最常见的是顺序一致性,意味着操作相互排斥排着序。而可线性化更强调部分到总体的组合,可线性化的是顺序一致的。前面提到的在支持X读Y写的存储单元就是一个并发对象。

操作的操作是数据的存储和获取。

操作的相互排斥保证了顺序一致性,并且似乎还是可线性化的(按书上说的是)。所以在考虑整个存储器的时候,这个并发对象提供多个存储单元的读写功能。也是正确的。

如今将这里的抽象的并发的存储器和实际的比較。

前面说到总线上的操作是相互排斥的,于是我们得到了一个非常NB的存储器,支持同一时候读写。可是这个福利是总线带来的。于是让存储器本身的设计减轻负担。(omg。之前被忽略的缓存呢?)。还要注意一点的是,存储器的存储单元是字节,总线上传输的是块(貌似是64位来着),而CPU读写的可能是1字节。2字节。4字节,8字节。这样不仅一方面在保证单个存储单元读写正确,并且还有一方面保证对齐的多个单元作为一个总体时的读写正确。

[进一步思考,更高级的并发对象呢?]

上文一方面描写叙述了x86体系中对内存的读写,还有一方面提出了并发对象,并发对象的正确性的概念。还观察了存储器怎样作为并发对象。

考虑一个问题。某个核上已经将某个指令译码。丢到乱序引擎中运行。同一时候还有一个核改动这条指令所在的内存。显然。这个改动(假定改动在瞬间完毕)不会导致还有一个核的又一次读指令。

这个问题本身的提法是错误的。

两个核上提到"同一时候"的概念是没有意义的。在共享存储器并发计算中我们假定不同的运行单元各自以不同的速度运行,且在随意时刻能够停止一个不可预測的时间间隔。我们无法去提"同一时候"。我们为什么会去考虑"同一时候"呢?由于两个核有数据共享。

一个核的指令数据,同一时候是还有一个核须要写的数据。我们在在存储单元的角度仅仅考虑别人读的时候给了什么值。写的时候存储了什么。而站在运行单元的角度来看,仅仅考虑读到的时候是什么值,写的时候写进去了。

假设两个运行单元(在这里和核。线程等价)没有不论什么共享的东西。在这一层我们没啥可考虑的了,仅仅能在更高的抽象层次上去看看并发、并行程度神马的。可是这是不现实的。

多核计算由于共享东西而变得复杂。要通过共享的东西来进行通信。同步。使得多个运行单元协调工作。

共享的东西能够是高层的抽象,可是落究竟层,仅仅能是一个一个存储单元了。最简单的同步就是使得两个运行单元相互排斥运行。前文的模型已经支持这种相互排斥了。已有的东西已经能保证正确实现peterson锁。(理论上的是这样。实际上还复杂着)

CPU指令分若干个层次。一般觉得是我们看到的一条汇编指令。可是指令还会被翻译为微操作。眼下的intel CPU上是4个译码单元,3个是单元译码器,1个是复杂指令译码器。这些操作被派发到乱序引擎后,每个运行port,还须要更细微的操作来完毕一条微操作指令。这里就和前面说到的读写存储器的操作有点差距。于是CPU提供一些相对高层一点的原子操作(lock指令),表现为汇编指令。在这些操作时锁定总线,独占存储器,包括读。写计算。全部的这些指令就像在单个运行单元上运行一样,同一时间仅仅有一个运行,后面运行的能观察到前面运行的结果。(至此,实现peterson锁是能够的了)。

如今另一些问题:CAS。store buffer,乱序运行,memory barrier。

CAS这种原子操作有点特殊。在指令中有"分支"存在。其意义在于提供无限大的一致数。

没有这种逻辑的时候,前面的那些原子操作,在N个核运行且须要相互排斥的时候。须要N个存储单元。而有CAS后就没这个问题了。

前面提到的写存储的操作。都是马上写的。store buffer的存在使得能够让写动作先缓缓。

x86的乱序运行使得一个单元的load操作能够提前到还有一个不同单元的写操作前。

这就是所谓的神马称作带store buffer的处理器次序来着。这样会带来什么问题?看store buffer的话。写操作可能没马上生效。

又看乱序,意味着影响在其他核去操作相应的存储单元时,所理解到的该核的操作顺序。所以引入memory barrier。memory前的操作应该生效,在其他核看到,该核的操作被memory barrier分为两部分前面的一定在后面的之前发生。所以问题本质在于:内部操作顺序对外部的可见性。

顺序。产生了因果,多核的协调正是追求某个因果关系。

在多核处理器级别上多核计算,貌似。大概。至少须要理解上述东西。

在更上面一层。比方C++11的多线程运行的内存模型。讨论大半天。一大堆order,事实上在解决这种问题:线程内部的操作顺序,能够被其他线程观察到。进而协调全局的次序。当操作顺序不须要被观察到时。意味着能够按单线程逻辑优化。当操作顺序须要被观察到时。各个线程按这个序协调工作。进而保证程序的正确性。

所谓的release-acquire等语义,就是在说在一个线程acquire到期望值时,还有一个线程release该值,且release前的动作生效。更细的则还有consumer,而更高层的才是相互排斥量神马的。而从C++的这层到CPU这层,还隔着个编译器呢。。。

不管你无锁还是有锁。多核编程中,这些都逃不掉。

PS:文章纯属自己YY,不负责后果。

推荐阅读《The Art of Multiprocessor Progrtamming》(多处理器编程的艺术)Maurice Herlihy, Nir Shavit著。金海,胡侃译。

补刀:

lock操作的全序使得多个核观察到的变量(存储位置)集的变化过程是一致的。多核基于这个共识,才干协作。一个核上的lock操作还有个彩蛋。一个核上按某个顺序操作,在其他核观察到相应操作时。顺序必定也是同样的:操作顺序是共识的一部分。

可是这种共识的要求有点强。所以。削弱一点后就是某个核上的操作顺序对其他核的可见性。这是memory barrier的必要性:内部操作顺序对外部可见(写操作的可见就意味着要使写生效)。除此外,在没有memory barrier和lock instruction的情况下,多核读写内存。还要满足一定的一致性:比方intel文档中的Stores
Are Transitively Visible。Stores Are Seen in a Consistent  Order by Other Processors。

总体看来:

运行模型:大家各自乱序运行。一起读内存,一起带store buffer地写内存。在运行过程中,每一个核能观察到一定的变化。观察到的不同结果满足某种相容性,即到某种共识。假设上述共识不够强,则用memory barrier。

假设还不能满足要求,还有原子操作。

多核协作的本质是共识。对某些变化达成一致,上面三个层次的共识由弱到强。

而共识中最重要的是顺序。强点的顺序是全局的全序。弱点的是让别人知道自己的顺序。

而多核和分布式有两点同样:

并发对象及其正确性。

要协作则要达成共识。

而共识中最重要的是顺序放到分布式中还成立吗?这个就说不准了,多核中有共享存储。可是。我们能够在分布式中引入类似的东西。比方一个协调者,这样,把分布式的问题转换为多核的问题了。

时间: 2024-11-06 11:25:08

多核编程杂谈的相关文章

Lambda&Java多核编程-6-方法与构造器引用

在Lambda&Java多核编程-2-并行与组合行为一文中,我们对Stream<Contact>里的每一位联系人调用call()方法,并根据能否打通的返回结果过滤掉已经失效的项. 应该注意到此时filter(..)中Lambda的写法有些特殊: // ....filter(Contact::call)// ... 按常理我们应该使用s -> s.call(),但是这里却将参数.箭头以及对参数调用方法全部用其类型Contact的方法标签(暂且这样称呼)call来代替,而这个::就跟

Python多核编程mpi4py实践

Python多核编程mpi4py实践 [email protected] http://blog.csdn.net/zouxy09 一.概述 CPU从三十多年前的8086,到十年前的奔腾,再到当下的多核i7.一开始,以单核cpu的主频为目标,架构的改良和集成电路工艺的进步使得cpu的性能高速上升,单核cpu的主频从老爷车的MHz阶段一度接近4GHz高地.然而,也因为工艺和功耗等的限制,单核cpu遇到了人生的天花板,急需转换思维,以满足无止境的性能需求.多核cpu在此登上历史舞台.给你的老爷车多加

多核编程 与 单核多线程编程的区别

1.锁竞争: 单核中,如果单个线程取得所,则获取CPU运行时间,其他等待获取锁的线程被阻塞.使用了锁,影响的只是枷锁和解锁的耗时,CPU始终运行. 多核中,若2个(更多)线程使用同一把锁,则会导致CPU饥饿.实际还是串行化执行! 2.线程分解和执行的区别: 对单核CPU,对客户端软件,采用多线程,主要是 创建多线程将一些计算放在后台执行,而不影响用户交互操作.(用户界面 & 其他计算 并行进行)提高用户的操作性能! 多核中,分别出多个线程,不再限于将用户界面操作和其他计算分离.分解多个线程使为了

精通lambda表达式:Java多核编程pdf

下载地址:网盘下载 内容简介  · · · · · · lambda表达式权威指南 <精通lambda表达式:Java多核编程>介绍Java SE 8中与lambda相关的特性是如何帮助Java迎接下一代并行硬件架构的挑战的.本书讲解了如何编写lambda.如何在流与集合处理中使用lambda,并且提供了完整的代码示例.你将学习如何通过lambda表达式充分利用当今多核硬件所带来的性能改进. 主要内容: ● 为何需要lambda,它将如何改变Java编程 ● lambda表达式语法 ● 流与管

网络分流器-网络分流器-多核编程的几个难题及其应对策略

网络分流器-网络分流器-多核编程的几个难题及其应对策略! 戎腾网络: 随着多核CPU的出世,多核编程方面的问题将摆上了程序员的日程,有许多老的程序员以为早就有多CPU的机器,业界在多CPU机器上的编程已经积累了很多经验,多核CPU上的编程应该差不多,只要借鉴以前的多任务编程.并行编程和并行算法方面的经验就足够了. 我想说的是,像涉及到网络分流器采集器功能的多核处理板业内统称为业务处理板,而多核机器和以前的多CPU机器有很大的不同,以前的多CPU机器都是用在特定领域,比如服务器,或者一些可以进行大

Linux环境下网络编程杂谈&lt;&lt;转&gt;&gt;

今天我们说说“Pre-网络编程”.内容比较杂,但都是在做网络应用程序开发过程中经常要遇到的问题. 一.大端.小端和网络字节序 小端字节序:little-endian,将低字节存放在内存的起始地址: 大端字节序:big-endian,将高字节存放在内存的其实地址. 例如,数字index=0x11223344,在大小端字节序方式下其存储形式为: 上图一目了然的可以看出大小端字节序的区别. 还有另外一个概念就是网络字节序.网络字节顺序是TCP/IP中规定好的一种数据表示格式,它与具体的CPU类型.操作

混合编程杂谈续——发布

上一篇简单的描述了一下混合编程中C++与python混合编程的操作,基本环节的搭建应该是没有什么问题了,这一篇说一下C++与python混合编程的发布问题,因为我们不能让客户和我们一样去了解程序的实现原理,他们要的只是一个结果,可以正常运行的程序,并不关心你的实现方式.因此我们要提供一个安装包,不依赖具体环境的可执行的程序. 在上一篇中我们实现了可以调用python脚本的环境,但是它的运行强烈的依赖环境,所以今天我们就要解决这个问题,将程序发布到其他别的机器上时不受限制. 今天在网上看了好多例子

混合编程杂谈

此文抛砖引玉,仅作记录,希望广大网友多多指正,谢谢! 最近接触了混编的项目,项目是02年开发的,当时使用的是VC6.0+TCL脚本,VC负责前台的界面显示,后台全部用TCL实现,由于公司规定不能截图出来,望大家海涵,大概描述一下工具的功能,主要用于检查某种负责通信的服务是否配置.运行等正常.大体功能如下:左边是局点的控制描述,主要记录有局点名称.局点IP.连接方式.用户名密码.是否使用内置FTP等信息,右边是每一个局点要执行的任务配置项,主要分为两个大项:健康检查和信息收集. 最近有一个新的需求

TCP网络编程杂谈

作为一名IT工程师,网络通信编程相信都会接触到,比如Web开发的HTTP库,Java中的Netty,或者C/C++中的Libevent,Libev等第三方通信库,甚至是直接使用Socket API,但是很多程序员都仅限于使用,对于使用的方式是否合理并没有特别深的理解,比如有一股脑的使用线程池解决问题的(虽然大部分情况采用多线程方案不会有什么问题,但是编程复杂度比起单线程提升了很多,线程开的太多也会导致切换过于频繁,性能未必有太大提升),也有始终用一条线程处理所有业务的,然后上线之后经常出现各种服