本申请要求于2018年12月4日提交的名称为“SYSTEM AND METHOD FOR HANDLINGDEPENDENCIES IN DYNAMIC THREAD SPAWNING FOR A MULTI-THREADING PROCESSOR”的美国临时申请16/209,663的优先权,其全部内容通过引用结合在本申请中。
具体实施方式
XMT(eXplicit Multi-Threading,显式多线程)处理器包括MTCU(主线程控制单元)和多个并行TCU(线程控制单元)。MTCU可以执行程序的串行段,并且管理并行模式和串行模式的变化。例如,MTCU可以是任何高性能处理器芯。TCU可以是在电力和硅的使用方面非常有效的简单的轻型芯。例如,TCU可以是5级有序处理器。XMT处理器具有两种执行模式:(1)串行模式,其中仅MTCU处于活动状态;以及(2)并行模式,其中MTCU和并行TCU二者均处于活动状态。在切换到并行模式之前,MTCU通过以两个寄存器“gr_low”和“gr_high”指定线程ID的范围来指定TCU中需要执行多少个线程。这些寄存器保存了线程ID的上下限,包括上限和下限。其通常由软件程序设置。TCU将执行满足以下方程式的所有ID:
gr_low≤线程ID≤gr_high方程式(1)
由于线程ID是唯一的,因此可以将不同的任务分配给不同的线程。每个线程可以根据线程ID进行不同的处理,因此在单个并行模式下,可以并行执行许多不同类型的任务。
在给定系统中,并行TCU的数目是固定的,并且可以多于或少于活动的线程的数目。如果活动的线程多于并行TCU的数目,则每个TCU将在开始时从gr_low开始获得一个线程,并且在分配的线程完成时,TCU将被分配新的线程ID。如果新线程ID等于或小于gr_high(有效),则TCU将继续执行由新ID规定的线程。如果新线程ID大于gr_high(无效),则该线程将根本不会被任何TCU执行,因为gr_low和gr_high值是所有TCU都将其禁用的全局值。然后,TCU将自身标记为“空闲”并且持续监视gr_high,因为gr_high可能会通过其他活动线程而增加,并且ID可以变得满足方程式(1)。如果该ID满足方程式(1),例如,gr_high寄存器增加,则TCU将执行ID并在完成时获得新的ID,并且这持续直到所有TCU变为“空闲”为止,此时XMT处理器切换回到串行模式。执行流程如图1所示。
如图1所示,执行流程可以被看作一系列状态:首先是串行模式105,然后是并行模式110,然后返回到串行模式115。最初在串行模式105下,仅MTCU 125是活动的。MTCU 125还将设置gr_low和gr_high的值。然后,MTCU 125将指定需要并行运行多少个线程,然后XMT将进入并行模式110,在该模式下,多个TCU 120被实例化并各自运行在有效线程ID范围内的一条线程。并行模式继续进行,直到所有具有有效线程ID的线程都已由TCU 120执行。然后,XMT恢复为串行模式115,在该模式下TCU 120停止运行。
在并行模式下,如果线程发现存在需要通过增殖更多线程来完成的工作,则这将增加gr_high来激活更多线程。增殖新线程的线程被称为父线程,而被增殖的线程被称为子线程。例如,运行软件程序线程的TCU可以执行指令来增殖另一线程,在这种情况下,TCU用作父级并且现在需要在另外的TCU中增殖子线程。通常,父线程需要基于其线程ID为子线程初始化一些数据。然而,在一些情况下,可能存在潜在的竞争状况(如图2所示),因为父线程需要子线程ID以进行初始化。为了获得子线程ID,将生成子线程(块205),并且一旦该子线程被生成,即使该子线程的初始化尚未完成(块210),该子线程也可能被执行(块215)。
美国专利第8,209,690号描述了一种解决该竞争状况的解决方案(如图3所示)。
在此在图3中,首先在块305处生成子线程。将在块310处初始化子线程数据,同时在同一时间在块325处为所有可能的子线程ID声明FLAG数组。在开始时将FLAG数组初始化为0。子线程将检查与其自身的ID即FLAG[ID]相对应的数组元素,并且将进行忙等待直到FLAG[ID]变为1,这意味着父线程已完成对子线程的初始化。父线程在产生子线程之后将获得该子线程ID,并且在对应FLAG[ID]为0时初始化该子线程。在块315处,父线程会在其完成初始化时将FLAG[ID]设置为1,这将使得子线程能够在块320处继续进行。然而,该方法效率低下,因为为了防止潜在的竞争,在每次生成子线程时,每个子线程均需要相应的附加数组。相比更高效的解决方案,每次都为子线程创建数组占用了不必要的内存。
本申请的各方面在不使用附加数组FLAG的情况下提供了该问题的解决方案。即,可以引入另外的寄存器“gr_child_id”,其将被MTCU初始化为与gr_high相同的值。本申请的各方面介绍了一种新颖的方法,用于使MTCU通过寄存器gr_high、gr_low和gr_child_id的组合来控制子线程的激活。回想一下,需要从父级执行子线程的竞争状况问题在于子线程可能在子线程数据初始化之前开始执行。
根据本文的一些实施方式,如果线程需要生成子线程,则其将从gr_child_id获取子线程的ID。子线程ID的编号将高于gr_high的值。此时,由于gr_high不递增,因此新的子线程不会生成,并且该子线程将不会被执行。然而,能够从gr_child_id获得子线程的ID,父线程能够使用获得的子线程ID来(在程序的已执行的父部分中)初始化子线程的任何输入数据。在父线程完成初始化之后,可以将gr_high更新(递增更高)以实际地成子线程。因此,第三寄存器gr_child_id的引入使父线程能够在执行子线程之前获得必要的子线程ID,以解决竞争状况。
在这种情况下,潜在的问题是生成的线程ID可能与初始化中使用的子线程ID不同。在图4的表中示出了示例。MTCU以线程ID从0到99的总共100个线程开始。在某时刻,假设线程0和线程5需要生成新的线程来处理额外的工作。例如,假设线程0的子线程ID为100,线程5的子线程ID为101。现在假设父线程5首先完成初始化,实际上,接下来父线程5能够通过递增gr_high来生成其子线程。然而,将gr_high简单地递增使得gr_high=100。这是父线程0的ID值,即使父线程5已经准备好,但父线程0可能仍未准备好执行其子线程。问题是父线程5已初始化ID等于101的子线程,但是生成的线程ID是100。如果父线程被编程成在其准备好执行其子线程时自动地自行改变gr_high,则可能会发生这种情况。通常,可能会出现这种类型的问题,因为父线程被赋予子线程ID的顺序可能与父线程完成初始化其各自子线程的顺序不同,从而使其准备执行其子线程的顺序可能不同。
为了解决这个问题,根据一些实施方式,可以引入gr_high控制器,并且其在适当时将使gr_high递增。gr_high控制器可以是配置成控制gr_high寄存器的任何变化的控制电路。根据一些实施方式,每个被TCU单独执行的父线程将其各自获得的子线程ID值发送至gr_high控制器,然后gr_high控制器将在向量child_id_vec中跟踪所收到的所有线程ID。例如,当当前gr_high为99时,child_id_vec的位0代表100,位1代表101,位2代表102,依此类推。为了解决上述问题,一种解决方案是,每次gr_high控制器接收到新ID值时,仅在所有低于新ID值的子ID已被接收,同时所有低于当前gr_high的值的子ID也被接收的情况下,gr_high控制器才会将gr_high的值增加到新ID值。因此,在前面的示例中,当gr_high控制器从线程5接收到101时,其仍不会使gr_high增加。在其从线程0收到100以指示父线程0已完成其子线程(ID为100)的初始化之后,因为接收到101至99之间(即,100和101)的所有线程ID(意味着全部这些线程的初始化完成),所以gr_high控制器将gr_high更新为101,这意味着此时将生成子线程100和101,并且两个子线程的初始化均已完成。
图5中的表已示出了此示例情况。检查步骤并回顾父线程的状态、gr_high控制器中的寄存器的状态和子线程的状态反映了前面描述的情况。在此示例中,向量child_id_vec具有四个位,其中最后一个位是位0。在其他示例中,向量child_id_vec可以具有多于四个或少于四个的位。每个位可以对应于未初始化的子线程,其中位=0表示子线程尚未初始化,而位=1表示子线程已初始化。child_id_vec中的每个位与哪个父线程对应取决于父线程被赋予子线程ID值的顺序。例如,如果父线程0首先获得子线程ID 100,则child_id_vec的最低有效位对应于子ID 100;如果父线程5接着获得子线程ID 101,则child_id_vec的第二最低有效位对应于子ID 101。通常,child_id_vec可以是足以跟踪未初始化的子线程的任意大小。
以这些方式,本申请的各方面实现了在不利用任何数组的情况下,在执行子线程之前可靠地获得子ID。通过利用所描述的附加寄存器并实现所描述的父线程、子线程和寄存器之间的过程,不需要实例化数组,从而为每个可能需要创建子线程的父线程节省资源。例如,如果要执行一百万个父线程,则旧解决方案将需要创建一百万个数组(每个父线程一个),以记录FLAG来确定何时可以安全地执行子线程。利用本文提出的解决方案将不需要此类资源。
在一些情况下,父线程需要确保发送到gr_high控制器的子线程ID小于或等于(gr_high+child_id_vec中的位数)的值。在上面的示例中,父线程需要确保发送到gr_high控制器的子线程ID小于或等于99+4=103。如果子ID大于该值,则父线程必须等待,直到child_id_vec中正在跟踪的子线程都被初始化然后被执行(例如child_id_vec=1111)。这将导致四个子线程全部开始执行,gr_high值将增加四,child_id_vec位将被重置为0000,并且这四个位现在将递增,以对应于大于gr_high的下四个值。如果父线程无法将其子线程初始化值拟合入child_id_vec中,则该信息可能会丢失,并且系统可能会导致死状态。然而,在大多数情况下,如果child_id_vec中的位数适当,则不应发生等待或仅很少发生等待。同样,可以通过简单地增加child_id_vec中的位数来缓解这种情况。
尽管在图5所示的示例解决方案中,仅两个父线程生成了被gr_high控制器管理的子线程,但是通常,本申请的各方面包括用于增殖子线程并解决本文所述的竞争状况的任意数目的父线程的解决方案。例如,从图5中描述的示例进行概括,如果存在两个以上的父线程被编程以增殖子线程,则以上每个子线程均可以增殖,并且gr_high控制器可以给每个子线程分配子线程ID,首子线程ID值比当前gr_high索引值大一,其余子线程ID值在首子线程ID值基础上依次递增,例如,如果gr_high为99,则子ID(1)=100,子ID(2)=101,子ID(3)=102等。gr_high控制器会将child_id_vec中的最低有效位分配给编号最小的子线程,之后把次最低有效位分配给编号次小的子线程,逐渐递增有效位和子线程编号并使之一一对应,直到child_id_vec中的所有位均与相应的子ID对应。至此,所有父线程都将能够获取其各自的子线程ID,以在其程序序列中使用。每个父线程将开始初始化其各自的子线程。当child_id_vec中的所有位当前都被占用时,需要创建子线程的任何父线程将向gr_high控制器提交请求,但不会收到ID,并且将等待直到之前的子线程完成初始化为止。
每当子线程中的第一个子线程完成初始化时,该子线程的父线程就会给gr_high控制器发送指示以表示初始化完成,例如将传回子线程ID作为指示。然后,gr_high控制器将更新child_id_vec中的相应位,以指示子线程已完成初始化。gr_high控制器将检查先前的次有效位是否也都已更新(为1),以便gr_high控制器将能够确保gr_high寄存器可以可靠地更新,以使所有已经正确初始化的子线程能够执行。gr_high控制器将等待以使得gr_high寄存器递增,直到child_id_vec中的所有先前的次有效位都更新(为1),则表明所有先前的子线程也已完成初始化。
当以上情况发生后,gr_high控制器将根据child_id_vec中连续的1的数目(从最低有效位开始)来更新gr_high寄存器。这会使上述所有已正确初始化的子线程开始执行。此过程会确保所有子线程在开始执行之前已完成初始化。然后,child_id_vec会清零已被执行的子线程所对应的有效位,并且此后任何新增殖的子线程将被再次分配高于当前gr_high值的子ID。该过程可以在软件程序的整个操作中重复。
如图所示,本申请的各方面通过不使用特殊的数组FLAG减少了以并行模式增殖新线程的开销。如果使用FLAG,则每个子线程生成需要至少执行一次存储器写操作和一次存储器读操作。本文介绍的新解决方案随着问题被放大,创建的子线程更多,可以看到更显著的效率提高。例如,如果需要一百万个子线程,则使用旧的数组解决方案执行该程序将需要读取一百万个元素,这非常昂贵。在新解决方案中,不需要存储器读取。
父线程需要将FLAG[ID]写入一,并且子线程至少需要读取FLAG[ID]一次,并且如果FLAG在以后重复使用,则子线程可能需要将FLAG[ID]清除为0。根据本申请的各方面,所有这些存储器操作被省去而几乎没有或没有性能损失,并且无需声明用于动态增殖的数组。
图6示出了多线程处理器的示例性架构。该示例性架构包括:主线程控制单元(MTCU);包括TCU、路由器和缓存模块的64个群集;八(8)个存储控制器(memorycontroller,MC)。
如图6所示,主TCU(MTCU)执行程序的串行部分,并且处理特殊的XMT指令,例如增殖和汇合指令。MTCU在并行段中将指令广播到所有群集,其中指令被复制到本地指令缓冲区,然后由群集内部的TCU提取。主TCU具有自己的缓存L0,该缓存仅在串行模式下处于活动状态并应用直写。当XMT处理器进入并行模式时,主TCU丢弃其本地缓存。由于选择了直写机制,因此刷新L0缓存的开销很小。当XMT以串行模式运行时,L0缓存是MTCU的第一级缓存,并且并行存储器模块提供存储层次的下一级,这类似于高级单处理器中的多级缓存层次。
群集是多个TCU(例如16个)以及随附的功能单元的集合。群集的框图在图4中示出。TCU可以在并行模式下执行线程。TCU具有自身的本地寄存器,并且它们是简单的顺序流水线,所述顺序流水线包括获取、解码、执行、存储器访问和回写阶段。TCU可以具有非常简单的结构,并且不会积极追求最佳性能。给定有限的芯片面积,当XMT拥有更多数目的简单TCU时,其总体性能可能会好于XMT拥有数目较少但更先进复杂的TCU,这是因为许多指令级并行(instruction levelparallelism,ILP)技术具有众所周知的收益递减效应。然而,XMT概念不会阻止TCU引入任何高级技术,因为XMT利用的线程级并行(threadlevel1parallelism,TLP)与ILP互不相关。与同时多线程(simultaneous multithreaded,SMT)处理器相似,TCU共享一些功能单元:乘/除(M/D)单元和互连网络端口。如果多个TCU试图使用同一个被分配的功能单元,则使用适当的仲裁将所有请求排队。群集具有一个通往互连网络的加载/存储端口,该端口由群集内的所有TCU共享。通过对尚未完成的存储操作进行计数,存储计数器被用来刷新存储操作。
在XMT处理器中,前缀和运算被非常高效地执行。前缀和单元的硬件实现可以接受来自多个TCU的二进制输入,并且执行时间不取决于向其发送请求的TCU的数目。群集中的PS TCU模块组合了来自TCU的所有请求,并且将一个请求发送到MTCU中的全局前缀和(prefix-sum,PS)单元。在一些实施方式中,全局PS单元包括或被配置成执行如本文所述的gr_high控制器的功能。它还负责将结果从前缀和单元分配到各个TCU。
存在例如64个独立的共享缓存模块,并且它们通过互连网络连接到群集。地址空间在这些缓存模块之间平均分配。这些并行缓存主要用于数据,因为常规TCU的指令由MTCU广播并存储在指令缓冲区中。互连网络是XMT处理器的非常重要的部件,并且需要在群集与缓存模块之间提供高带宽低延迟的通信。
图6的示例性多线程处理器支持MIPS I ISA的子集以及一些特定于XMT的指令。特定于XMT的指令包括增殖、汇合,s增殖(对于单个增殖(single spawn):在并行模式下生成附加线程)、ps、psm以及用于广播、预取和只读缓存的指令。
本申请的各方面不限于图6的示例性多线程处理器,并且可以应用于其他并行计算架构。
除非另有明确说明,否则如专利文件中所使用的,术语“一”或“一个”在本文中被用来包括一个或更多个实例。最后,除非另有明确说明,否则如本文所用,连词“或”是指非排他性的“或”。
本申请是说明性的而非限制性的。根据本申请,进一步的修改对于本领域技术人员将是明显的,并且旨在落入所附权利要求的范围内。