时序相关任务的并行计算解决方案讨论 (2)

分析去“时序相关”的可能

在回答主要问题之前,我们还是需要再三分析问题及其数据处理逻辑,看是否可以将数据处理逻辑由时序相关降为时序无关,这样并行计算的效率无疑会更高。

前一部分对时序相关的讨论提到“时序相关取决于数据处理的业务逻辑或处理算法”,改变数据处理的算法是有可能实现去“时序相关”的。还是以第一部分提到的和告警相关的设备简单状态机为例,假如变具体实现为:保存两个计数,alarmSetCount统计告警产生事件的次数,alarmClearCount统计告警消除事件的次数,则设备当前状态为:

alarmSetCount – alarmClearCount > 0 ? 故障 : 正常

处理器DAG

前面只是用一个简单的例子说明了去时序相关的可能性。但实际时序相关问题要复杂得多,而且大部分是不可降级的。

处理时序相关任务的处理器“群”一般是一个两层结构,第一层是任务调度层,其中的处理器负责根据相关因子,将任务分发给第二层某个具体处理器。第二层为任务计算层,其中处理器负责具体任务的执行,并行计算主要体现在第二层上。如果数据入口只有一个,则调度层上只有一个处理器,它和计算层处理器全部相连,如图3所示,不妨定义为单调度器模式。

单调度器模式

3        单调度器模式

 

实际情况数据入口往往会有多个,一个入口对应一个调度器,构成多调度器的模式。根据两层之间的联系,多调度器模式又可以分为分离的多调度器模式和混合多调度器模式。分离的多调度器模式(如图4)的前提是所有时序相关任务的数据只有一个数据入口,所以时序相关任务不可能出现在不同的调度处理器里。分离模式等效于多个单调度器模式的横向迭加。任务计算层中每个处理器只对一个调度器负责,对于进入调度器P[s]的任务,候选第二层处理器为P[s, 1] … P[s, ts],并不是所有第二层处理器全集,这样的设计虽然不利于计算层的负载平衡,但可以提高任务调度的效率。

分离多调度器

4        分离的多调度器模式

 

混合模式(如图5)则正好相反,时序相关任务的数据可能从不同入口进入,因此每个调度器和第二层处理器全部相连,任务可能分发给第二层任何一个处理器。这样的模式更有利于计算层的负载均衡,但在任务调度时可能消耗更多。

混合多调度器

5        混合的多调度器模式

 

以上讨论都是从横向对数据进行划分,讨论并行计算的可能。时序不相关的任务可以被分配到不同的处理器,以达到高效并行处理的目的。计算层横向扩张的处理器数目上限理论上就是相关因子集合I的基数(I的元素个数) (参见一般性归纳部分对相关键的讨论),实际可能更小一些。当计算层处理器数目达到上限后,再增加处理器就不能再提高并行计算的效率了,反而可能会导致处理器的闲置。

如果任务运算量较大,计算时间远大于通信时间,则可以从纵向对功能进行划分,将一个任务分为若干子任务,将一组子任务交给一个处理器链去执行。

这样,原来计算层中的每个处理器可以扩展为一个处理器链。整个第二层就形成一个处理器距阵,纵向有连接,横向彼此独立。假设在原来如图3所示的单调度器模式下,一个第二层处理器执行一个任务Task的平均时间为a,从调度层看,1/a是单个第二层处理器的吞吐效率。

现在将Task划分为s元素的子任务偏序集{ Subtask[x] | x [1, s] },并在第二层处理器链中串行执行:对任意xSubtask[x]P[t, x]中执行,Subtask[x]执行完毕,Subtask[x+1]才在P[t, x+1]中执行,直到完成所有子任务。各Subtask[x]执行时间为a[x],理论上有 a = s x=1  a[x],其中a就是上面给出的单个处理器执行一个任务Task的时间。再假设d[x]Subtask[x-1]Subtask[x]之间的通信时间,则一个任务实际总的执行时间为a’ = s x=1  a[x] + s x=1  d[x] = a + s x=1  d[x] > a

但任何一个第二层P[t, 1]只花费了a[1]的时间执行了Subtask[1],把中间结果交给后续处理器后,就能接下一个任务了,所以从调度层看,每个第二层处理器链的吞吐效率不会是1/a’,但也不是1/(d[1]+a[1])。因为考虑到木桶原理的效果,比如存在一个x,其对应的Subtask[x]的执行时间a[x]与通信时间d[x]之和为所有子任务中最大值,则从调度层看来,单个处理器链的任务执行时间为 max (d[x]+a[x]),吞吐效率为1/max(d[x]+a[x])x [1, s]

鉴于a = s x=1 a[x],理论上当任何一个a[x]都相等,且等于a/s时,处理器链执行任务的时间最短,为a/s + dd是子任务平均通信时间。这个推论告诉我们,在划分子任务时,不仅要以功能为依据,还要照顾执行时间,将每个子任务的运行时间分割得大致相当。

另外,采用距阵模式有一个苛刻的条件,就是待解决问题是一个单任务问题。否则不同类型的任务未必能够划分出相等数目的子任务。

矩阵模式

6        2距阵模式

调度算法的讨论

调度算法的逻辑在第一层调度处理器中被执行,它的中心任务就是根据相关因子,决定一个具体任务应该被分配到哪个第二层处理器执行,以保证所有时序相关任务被偏序执行。在这一部分,调度算法只考虑基于任务提交时间的提交时序,假定任务的提交时序与它的本质时序是一致的。

以下讨论基于单调度器模式,对其它模式经过调整也可以适用,不会有本质的修改。

首先讨论一种通过对相关因子取模来选择计算层处理器的算法,不妨简称为模算法。假设计算层横向维度(也就是处理器数目)m,处理器从0m-1逐个编号,对于任意一个相关因子为i的任务,目标处理器编号为 t = i % m, t [0, m-1]

模算法是一种静态调度算法,它的优点是策略简单,执行效率也很高。它通过取模将所有相关因子相同的任务映射到同一个处理器进行串行运算,以保证它们的偏序关系,但串行其实比偏序的条件更苛刻。在大部分情况下,模算法能较好的处理负载平衡,但我们也能够很容易的找到下面的特例,使模算法的调度效率降低。

假设计算层横向维度为3,问题的相关因子集合I = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13}。某个时间段内,和各个相关因子对应的任务数如下表:

i

1

2

3

4

5

6

7

8

9

10

11

12

13

任务数

0

12

309

1

0

20

1

31

147

0

0

284

0

处理器#

1

2

0

1

2

0

1

2

0

1

2

0

1

 

则各个处理器在该时间段内需处理的任务数分别为:

       Task Count [P0] = 760

Task Count [P1] = 2

       Task Count [P2] = 43

可见P0处于严重的忙碌状态。而且这样的假设完全是有可能的,只要想象一个网管问题中,被监控网络中许多设备处于关闭状态。

在上面这个特例中,模算法调度效率降低的关键原因是因为模算法将偏序执行的条件强化为更苛刻的串行执行。而实际上只要保证偏序,相关因子相同的两个不同任务并非必须在同一个处理器中被串行执行。为进一步讨论的方便,在此提出任务的生命周期(Lifetime),它指该任务被提交到计算处理器这一时间点开始,到最终被执行完成并从处理器移除这段时间,任务的生命周期可能包括两部分:任务缓存在处理器中等待被执行的时间和任务执行时间。假如在并行执行的条件下,Task[i, m]的生命周期PLT[m]Task[i, n]的生命周期PLT[n]没有交叠(PLT[m] PLT[n] = ø),则两个任务可以分配在不同的处理器,否则(PLT[m] PLT[n] ø),就必须将它们分配到同一个处理器,串行处理,保证LT[m] LT[n] = ø

基于上面的讨论,提出一种动态调度算法,可以提高负载平衡效率。该动态算法需要为每个处理器引入一个对应的可以保存键值对的registerMap容器对象,其中每个元素的键为相关因子,值为任务计数。下面是该算法用例的简单描述:

Basic Flow为任务Task[i]分配处理器 (i为任务相关因子)

1. 在一个循环中,对每一个处理器P[j],检查其对应的registerMap[j]

1.1 如果registerMap[j]中已存有待分配任务Task[i]的相关因子i,就将Task[i]分配给P[j],且执行Alt Flow#1,在register[j]中登记,并跳出循环;

1.2 否则,检查下一个处理器P[j+1]registerMap[j+1]

2. 如果第1步循环结束也没有找到目标处理器,就将任务分配给当前等待执行任务数最少的P[x],且执行Alt Flow#1,在registerMap[x]中登记相关因子i

Alt Flow#1登记相关因子

1.如果registerMap中已经保存了指定相关因子,就将对应的任务计数值加1

2.否则,在registerMap中添加该相关因子,对应任务计数值初始化为1

Alt Flow#2 注销相关因子

当处理器执行完成一个任务,就要执行情况,在对应registerMap中注销相关因子:

       1.在registerMap中将指定相关因子对应的任务计数减1

       2.如果对应任务值为0,就从registerMap中删除该相关因子。

从算法描述可以看出,动态算法的运行时间要比模算法长得多。

保证时序的更多考虑

讨论任务调度算法的假设之一就是任务的提交时序与本质时序是一致的。但实际上,提交时序不可能和任务的本质时序永远一致。我们要考虑怎样对此进行弥补。

在讨论之前,假定任务处理的数据所带的时间标签即反应了本质时序,有两种弥补的方案。第一种是数据过时检验(stale data check),每个相关因子对应一个最新任务执行时间。每执行一个任务,就用数据的时间标签刷新最新任务执行时间。而在执行每个任务之前,用任务数据的时间标签和最新任务执行时间比较,如果前者早于后者,就认为待执行任务的数据已经过时(stale),该任务被丢弃。数据过时检验的前提是具体问题及其实现允许丢弃数据。比如在计算设备状态的问题中,我们只关心设备的最新状态,所以在已知设备当前状态前提下,如果又收到前一秒的数据,就会不作为过时数据丢弃。

另一种是任务局部排序(locally sorting),第二部分已经说明时序相关任务理论上是一个无穷集,实际处理不可能对任务全集进行排序。任务局部排序要求处理器在接到任务后,并不立即执行,而是缓存一段时间,在这段时间里,后续任务又会陆续进来,处理器最后将这批任务排序并处理。任务局部排序的前提是具体问题及其实现允许数据处理有一定时延。任务局部排序仍然要处理落到缓存时间之外的过时数据。

 

你可能感兴趣的:(设计模式,数据结构,算法,J#)