最近一段时间,有关超算的话题沦为热门,一时间大家都开始辩论超算,然而,笔者找到在所有这些辩论中,从没在任何时间任何地点找到任何人得知就连小学生都常常回答的问题:超算究竟是怎么算数的?被迫说道是一件真是的事情。 【并行计算怎么算数】 我们告诉,单个CPU核心不能串行计算出来,也就是一条一条的把机器指令读出来继续执行。要解读的一点是,串行继续执行并不回应指令顺序继续执行,函数调用指令可以让CPU跳出其他地址继续执行,但是整个过程CPU只在继续执行一个单一的指令流,也就是一个线程,thread。
某个线程已完成某种任务,而线程对应的代码中的多个函数又各自分工已完成对应的工序。要想要同时继续执行多个线程的话,有两个办法,一个是加设额外的一个或者多个CPU,这样,在时间上可以做Parallel/分段,同一时刻有多个任务同时继续执行;另一种办法则是让单个CPU继续执行一段时间线程1,然后再行擅自函数调用到线程2继续执行一段时间,然后再行跳跃返回线程1,这样就可以构建多个线程的Concurrency/所发,但是却不是分段,因为同一个时刻还是只有一个线程在继续执行,只不过每个线程继续执行的时间十分较短,一般比如10ms的时间,就不会函数调用到其他线程继续执行,这样从表面显然,一段时间内,多个线程或许是“同时”继续执行的。
前者的方式看起来性能更高,但是其有2个悲惨的代价,第一个是线程之间的实时,第二个是内存一致性。如果多个线程运营在同一个核心上,那么它们不能一个相接一个的继续执行,继续执行线程1时线程2不有可能获得继续执行,如果线程1和线程2要操作者同一个变量,那么就轮流操作者,会有问题。但是多个线程运营在有所不同的核心上,事情就再次发生相当大变化了,比如有两个线程,都必须操作者某个变量,比如同时运营a=a+1这个逻辑,希望结果是线程1对a特了1,线程2要在线程1输入结果的基础上之后+1,而由于这两个线程运营在两个独立国家核心上彼此之间没协商,有可能造成线程1写的a的初始值0,特1之后还没有再也将近期结果变更到a所在的地址之前,线程2也写了a的初始值0,特1之后也尝试载入某种程度的地址,最后a的结果是1,而不是希望中的2。
解决办法则是对变量a特物理地址锁住,当某个线程操作者a之前,再行将锁住(也是个变量)置为1,其他线程大大的扫瞄锁住是不是早已被置为0,如果是1则回应其他人正在操作者a,如果是0则回应其他人早已获释了,那么其将锁住改回1,也就是上锁,自己操作者a,此时写的a就不会是被其他线程改版之后的近期数据了。这个过程叫作Consistency。所以,如果多个线程之间几乎独立国家各腊各的,没任何交互,这是最理想的场景,这就像多台须联网的独立国家的计算机各腊各的一样,只不过共用了CPU和内存。
然而,如果用于了内存,又用于了分享变量,事情又变得复杂了。线程1所在的核心1抢走变量a的锁住之后不会将a的内容内存到核心1的内存中,改版了a内容之后,该改版也仍然回到内存中而不是被flush到主存。其获释锁住之后,线程2抢走a的锁住并将a读取核心2的内存,此时如果不做到任何处置,核心2从主存中写的将是a的旧内容,从而计算出来错误。
可以看见,即便是用于了锁来确保Consistency,也无法防止内存所带给的一致性问题,后者则被称作Coherency。 Consistency由软件来负责管理,而Coherency则要由硬件来负责管理确保,具体做法是将每一笔数据改版实时广播过来给所有其他核心/CPU,将它们内存中的旧内容终止,接到其他所有核心的对此之后,该改版才被指出顺利。
所以核心/CPU之间必须一个超低时延的网络用作支撑这个广播。这个过程对软件几乎半透明。除了必须广播终止外,当其他核心必须采访该变量时,享有该变量近期内容的核心必需作出接收者将该内容启动时到收到采访催促的核心。
在很早期的SMP/UMA架构下,由于那时的SMP总线本身就是一个广播域,任何核心的访存催促都会被其他所有核心收听,还包括改版了某个地址、加载某个地址,这样很天然的可以构建Coherency,比如,当某个核心改版了某个地址之后,其他核心后台默默地的收看(或者说嗅探,Snoop),并在自己内存中查找自己是不是内存这个地址的内容,有则终止无则不动作。当某个核心发动对某个地址读书的时候,其他核心收看之后也默默地的搜寻自己的内存想到否有该内容近期版本,有则用于类似的信号守住总线并压制主存控制器对总线的守住,将数据回到到总线上,与此同时主存控制器也收看该内容并将该内容实时改版到该地址在主存中的副本。此时,该地址将享有三个副本,分别坐落于:之前内存它的那个核心的内存、刚读书它的那个核心的内存、主存,而且内容完全一致。
如果此时之前内存它的那个核心再度发动读书操作者,就没适当将读书催促发送到总线上,而浪费电,同时也浪费其他核心的搜寻运算花费的电能以及对其他长时间内存采访的守住。所以人们想了个办法,每个内存条目(CacheLine,内存行)减少一个字段,专门用来叙述”该内存当前正处于什么状态“,上述状态称作Share态,而如果有人改版了某个地址,其他核心嗅探到之后,之后将自己内存里这份内容改回”Invalid“态,而刚改版内容的那个核心里内存的该条目被改回”Modified“态,Invalid态的条目早已终止,再行读书就得回头总线,M态的条目可以必要读书,因为此时没其它人有比你新的内容了。
如果加电之后某个核心第一个抢走到总线并发动该地址的读,则读取之后该条目就是Exclusive态,因为只有它一个人内存了该条目,当另外核心再行发动读书之后,该核心嗅探到这个事件,于是将自己内存里的该条目发送给刚才发动读书的核心,那个核心从而告诉其他核心也有该内容,于是两个核心一起将自己本地的该条目改回Share态,这两个核心中任何一个如果再行发动该地址的读,就不必回头总线了,必要内存击中。可以看见,当某个核心必须采访的数据在其他核心的内存中时,硬件不会自动传送这份数据,软件显然需要关心。
所以多个线程之间提到分享变量的时候,必要提到才可。 上述方式被称作MESI协议,其目的是为了提高效率,不必须每一笔采访都回头外部总线。后来过渡到NUMA架构之后,NUMA是通过一个分布式互相交换网络来广播实时这些消息以及展开变量内容传输的,由于该网络并非一跳跃往返的广播网络,所以过滤器不必要的广播就更为最重要了。有所不同的CPU厂商有有所不同的方式,MESI协议也有不少变种,比如MESIF等等。
另外,由于丧失了天然的总线嗅探机制,如果某个内存行正处于Invalid态,加载该内存行之前硬件必须收到Probe操作者,探索,主动广播这个Probe催促给所有核心的内存控制器,内存了该行的内存控制器不会回到近期数据并将自己该行的状态改回S态。如果要改版某行,该行本地正处于E或者M态则必要改版,正处于S态则必须收到Probe催促终止其他内存中的该行。总之,当MESI遇上NUMA,就是个非常复杂的状态机,笔者就不之后进行了,要想要与笔者深聊NUMA和CacheCoherency,可拒绝接受购票面聊,前提是你得拿走笔者好像上的干货来咱们交换一下。
本文来源:七七彩票-www.firstsgusa.com