具体实施方式
作为举例,一个应用程序具有11个函数或者说例程main()、F1()到F10(),它们的调用关系如图1所示。按照传统的流水线处理技术,是对各函数进行静态的分割。例如,F1()由处理器#1执行,F2()到F6()以及F5()的进一步函数调用由处理器#2执行,F7()及其以后的函数调用由处理器#3执行。如背景技术部分所述,这种静态分割的适应性差,在应用程序的实际执行过程中,随所处理的数据的不同,各处理器的任务有可能并不均衡。例如,当F4()不调用F7()时,此时处理器#3对该应用程序而言就空闲了,而处理器#2需要处理比F4()调用F7()时更多的任务,也就是要多处理F5()、F6()等。
因此,针对这种问题,本发明提出了一种新的流水线处理方法。即,在分割子任务并分配给各处理器时,使各处理器处理的子任务有一定程度的交叠,即交叠部分的子任务由多个处理器“共享”(步骤702,图7)。在任务执行的过程中确定各处理器的状态(步骤704,图7),据之动态地决定交叠部分由共享交叠的子任务的处理器中的哪一个来执行(步骤706,图7)(下面称为子任务动态调节),从而实现多处理器之间的动态均衡。
仍以图1所示的应用程序为例,如图2所示,根据本发明的方法,函数F1()由处理器#1和处理器#2共享,函数F4()及其对F5()、F6()的调用由处理器#2和处理器#3共享。这样,如果在实际执行过程中处理器#2比处理器#1更加空闲,则可以由处理器#2执行F1();反之由处理器#1执行;同样,如果处理器#3比处理器#2更空闲,则可以由处理器#3执行F4()及其对F5()、F6()的调用,反之则由处理器#2执行。这样就实现了处理器#1、#2和#3之间的动态均衡。
在上面的例子中,交叠部分的子任务仅由两个处理器共享,例如F1()由处理器#1和处理器#2共享,F4()及其对F5()和F6()的调用由处理器#2和处理器#3共享。但是,本发明并不局限于此。如果有必要,同一个子任务可以由更多的处理器共享。
在程序的实际运行中,子任务的动态调整可以由多种方法来完成,关键是每一个处理器无论通过何种手段应当知道自身及其邻居的实时工作负荷比,而共享的那部分子任务总是应当送往当前具有相对较低工作负荷的处理器。其中一种可行的方法是:在共享的例程中植入代码(instrumentation)来进行动态调节。代码植入是分析目标程序或者对目标程序进行优化的一种典型手段。通常代码会被植入到某些函数的入口或出口。也就是说,植入代码以后,原来的程序在执行/释放之前,会先执行被植入的代码。在本发明中,将代码植入共享例程的出口(或者入口),被植入的代码负责根据处理器的“闲/忙”状态,动态的决定被共享的那部分子任务由谁来执行。原则是,当前哪个处理器更闲,就谁来执行。例如,这些代码可以负责查询关联处理器的任务队列(TQ)(包括运行当前代码的处理器的任务队列)的状态,根据负载均衡的原则判断是否接纳后面的共享任务。如果接纳,则继续,完成共享的任务;如果否,则把共享的这部分任务移交给流水线后面的处理器来做,同时,为该处理器准备处理当前数据(例如,在DPI应用中是数据包,例如IP包)所需要的上下文(例如源数据和中间数据)。如果还有要处理的另外的数据(例如,在DPI应用中,还有下一个数据包),所述关联处理器自己则切换回去处理所述另外的数据(下一个数据包)。
下面是用伪码表示的代码植入的一个例子。仍沿用图2所示的例子,假设函数F2()被分配给处理器#2执行。在函数F2()的末尾植入代码,根据处理器#2和处理器#3的等待队列长度来判断是由处理器#2还是由处理器#3来处理在F2()中调用的函数F4()。
F2()//假设函数F2()被分配给处理器#2执行
{
…
//下面是在函数F2()的出口注入的代码
If(TQ长度(处理器#2)<TQ长度(处理器#3))//
比较两个处理器的任务队列长度
{F4();
为下一个处理器准备数据(1);//参数“1”代表同时
需要完成共享任务;
};
Else
为下一个处理器准备数据(0);//参数“0”代表共享
的任务由下一个处理器执行,当前处理器仅仅准备数据
Return;
}
提供处理器任务队列长度的API(例如上面的伪代码中的函数“TQ长度()”)可以由OS/运行时环境或者硬件驱动器容易地提供。
如前所述,子任务的动态调整可以由多种方法来完成。作为代码植入以外的另一个例子,例如有守护进程法。
如图9所示,假设任务的划分仍如图2和图6所示,也就是子函数F1()由处理器#1和#2来分担,子函数F4()(以及其所有支路子函数F5(),F6()和F8())由处理器#2和#3来分担。守护进程法的原理是:
1)额外地为每个处理器设置一个“守护进程”,也就是图9中的D1(),D2()和D3();
2)它们常驻对应的处理器#1、#2和#3,负责监测相应处理器的状态,并根据处理器当前的负载情况,动态地控制分处于相邻的两个处理器里的“互斥流程选择开关对”,让其中一个处理器(如负载较低的一个)处理共享的子函数,而另一个处理器则绕过该共享的子函数,从而达到动态调节的目的;
采用守护进程的方法,用户的运行代码就无需植入特定的代码,只需要根据守护进程的接口,提供“可以用于动态调节的子函数”的标示和可被控制的函数开关即可。
此外,守护进程之间,例如流水线上级/下级对应的守护进程之间(如图中的D1()和D2()之间),还需要有通信的接口,一来用于在守护进程之间交互数据流向的信息,二来,还需要将动态调整的决策告诉下游守护进程,以免任务的漏做或重做。
具体来说,例如,如图9所示,守护进程D1()、D2()和D3()监视各子任务的执行以及各处理器的状态并相互通信,从而决定分担的部分子任务由哪一个处理器执行。例如,如果D1()决定F1()由处理器#2执行,则D1()发出指令,在#1中不执行F1(),并将此信息以及相关中间数据告知D2(),后者则发出指令,由#2执行F1()。依此类推。
可以注意到,在上面对代码植入的说明中,就处理器的状态而言使用的是处理器任务队列长度,这只是一个例子。事实上,在现有技术中,确定处理器状态的方法有很多。比如可以修改处理器(CPU)的硬件,使之能够提供它的状态,或者使用更复杂的动态任务预测的方法。又比如,无论是在视窗还是在Linux/unix操作系统中,都有监视CPU负载的工具。前面所讨论的任务队列的例子是在OS之上的用户应用层面的处理方式,是在任务层面上反映CPU的负载和任务需求之间的关系,从而确定CPU的忙闲情况。此外,还可以监视CPU的指令队列。现代的CPU都是流水线的工作方式,流水线最开头,有一个指令队列,用于存放从OS拿到的要执行的指令(这个队列很小),这个队列如果长时间都是满的,或者较长,说明CPU负载很重,跟不上处理的需求,反之,如果队列是空的,或者较短,说明负载轻。
除了上述的CPU指令队列读取法(微观粒度)和系统级的对应CPU的任务队列状态读取法(宏观粒度)以外,还有更多的方法可用于确定处理器的状态。例如,可以在CPU里面设立一个可配置的计时器(Timer)和汇报模块,以指定的时间或者运行的指令数目为周期,向系统主动提供该CPU或者该CPU核的状态。区别于前面的两种方法,该方法主动向系统汇报状态,而非被动地回答来自系统的状态查询,可以省却一定的系统开销。其效果与前两种方法的区别类似于轮询和中断的区别。
对于CPU的状态的确定,除了上面所述的非常精确的实时查询以外,还可以通过统计CPU的吞吐量和/或处理延时来获知。具体地,我们可以根据送给CPU的任务和它的完成情况的统计数据,来得知这个CPU是否被利用得很充分。原则上,我们可以认为,一个对所分配任务响应得很慢的CPU应该是高负载运行的;相反,对大部分任务都能以很小的处理延时完成的CPU应该是较为空闲的。具体操作上,我们可以通过在递交任务给CPU的时候记录系统时间,在完成的时候根据当前的时间和进入时间的差计算处理延时,这样就可以通过各个CPU处理延时的比较,来确定谁闲谁忙。
上面所讨论的分割子任务并分配给各处理器的步骤(步骤702,图7)可以用现有技术的任务分割方法来执行,只不过,按照本发明,需要使被分配给各处理器的任务存在在执行时动态分配的交叠部分。这只需要简单地移动传统任务分割方法中每一部分子任务的开始点和结束点即可。
任务的分割一般是针对关键路径进行分割的,因此要基于对关键路径的认识。关键路径一般是指在程序的函数调用图里面耗时最多的一条调用链。见图4,其中实线框及实线箭头所示为关键路径。其中,main()→F1()→F2(),这都是一对一调用,自然包括在关键路径里,而F2()可能会调用F3()或F4(),其中假设F4()在运行期间总的运行时间(大致等于调用次数乘以单次调用的运行时间)要多于F3(),因此F2()→F4()属于关键路径的一部分,而F2()→F3()不属于…依此类推。作为举例,图4所示关键路径为:main()→F1()→F2()→F4()→F7()→F9()→F10(),就是所谓的关键路径。当然,也可以考虑使用其他标准来确定关键路径,例如以代码长度为标准(关于代码长度请参见下文所述)。在确定了关键路径以后,即可以使用传统的方法进行如图1所示或者如图2所示的任务分割。
对于某一个应用来说,关键路径可以是已知的。例如,编程时保留了关键路径信息,或者在以前分析过关键路径,或者关键路径可以由外部工具提供。
而对于关键路径未知的应用,首先需要对应用程序进行概要分析(profiling)(步骤802,图8),以获取应用的调用关系图(call-graph)(图8中的804)。这一步有许多现有的工具可以使用,例如用IntelvTune(http://www.intel.com/cd/software/products/apac/zho/vtune/index.htm)、GNU gprof(http://www.gnu.org/software/binutils/manual/gprof-2.9.1/gprof.html)或者oprofile(http://oprofile.sourceforge.net/news/)等应用程序/代码分析工具。图3所示为图1及图2所示应用程序的调用关系图。在大多数应用中,尤其是在DPI应用(作为一种流处理)中,一般极少看到向前函数调用。换句话说,“调用关系图”倾向于看起来更象“树”而不是“图”。这对于子任务分割是有帮助的。例如,图3所示的树实际上是一个图,因为其中F8()在F5()下和F7()下都被调用,但为了分割任务和描述的方便,可以将其表示为似树的形式。
然后,根据调用关系图确定数据处理的关键路径(步骤806,图8)。在找到关键路径以后,即可以使用传统的方法进行如图1所示或者如图2所示的任务分割。
在本发明的一种优选实施方式中,为了使处理器的负载更加均衡,提出了关于任务分割的更为优选的实施方式,以便更加准确地均匀分割任务,并更加恰当地确定在处理器之间共享的子任务。
为此,需要进行进一步分析,找出关键路径中每一个函数的“自身时间(selftime)”(步骤808,图8)。
这里,“自身时间”是指特定函数本身花的时间,包括其所有支路子函数中的关键路径所花的时间。例如,在图4中,就F4()而言,其“自身时间”不包括其在关键路径上的子函数F7()所花的时间。F4()调用的不在关键路径上的子函数即为支路子函数,即F5()(F5()又进一步调用F8())以及F6()。如果将F4()视为主函数的话,类似于前文所述,也存在一个关键路径。这里,假设F5()→F8()的时间长于F6(),则F4()的支路子函数关键路径为F5()→F8()。也就是说,在图4的例子中,F4()的自身时间为F4()本身所花的时间加上其支路子函数关键路径F5()→F8()所花的时间。又例如,对于F2()或者F7(),由于它们调用的子函数除了在关键路径上的之外只有一个调用链,因此该调用链就是支路子函数关键路径。现有技术中也有很多工具可以分析得到函数的自身时间,例如前述概要分析工具。
按照一种实施方式,在得到所述自身时间之后,即可以按时间平均的原则对任务进行分割,将分界点上的例程由前后两个处理器共享(步骤810,图8)。例如,如图5所示,代表各函数的方框线的宽度示意性地表示自身时间。仍按照图1和图2的假设有三个处理器#1、#2和#3,可以按照累积自身时间将关键路径大致三等分(由于累积自身时间是以函数为单位进行计算,一般不可能绝对等分)。可以假设自身时间的三等分点在F1()和F7(),则可在这两个函数处进行任务分割,并将这两个函数F1()和F7()(包括其支路子函数)在对应的在流水线上相邻的处理器之间共享。
作为“自身时间”的替代方案,可以利用“代码长度”(code length)进行任务分割。为此,需要找出关键路径中每一个函数的代码长度(步骤808,图8)。代码长度是指函数内指令代码的行数,或者可以理解为执行一段程序需要的CPU汇编指令的数量。与自身时间一样,函数的代码长度也包括其支路子函数关键路径的代码长度。代码长度很容易通过反汇编工具得出来,例如前文所述的Intel vTune。在得到所述代码长度之后,即可以按代码长度平均的原则对任务进行分割,将分界点上的例程由前后两个处理器共享(步骤810,图8)。例如,如图5所示,代表各函数的方框的横向宽度示意性地表示代码长度。仍按照图1和图2的假设有三个处理器#1、#2和#3,可以按照累积代码长度将关键路径大致三等分(由于累积代码长度是以函数为单位进行计算,一般不可能绝对等分)。可以假设代码长度的三等分点在F2()和F7(),则可在这两个函数处进行任务分割,并将这两个函数F2()和F7()(包括其支路子函数)在对应的在流水线上相邻的处理器之间共享。
根据一种优选的实施方式,在步骤808中可以同时确定自身时间和代码长度,从而综合考虑自身时间和代码长度进行任务的分割(步骤810,图8)。仍以图5为例,可以分别按照自身时间和代码长度将关键路径大致三等分,仍然假设自身时间的三等分点在F1()和F7(),代码长度的三等分点在F2()和F7()。那么,可以参照这两种三等分点来进行任务分割。作为举例,可以考虑将这两种等分方式的对应的等分点之间的函数在对应的相邻处理器之间共享。在图5所示的例子中,就是将F2()和F7()(包括其支路子函数)在对应的在流水线上相邻的处理器之间共享。
根据本发明的更为优选的实施方式,在进行任务分割时,在上述自身时间和代码长度等分点的基础上,可以应用一些启发式规则或者说约束条件。这些规则包括但不限于:
1.具有较大代码长度但是较短自身时间的例程最好不在处理器之间共享(例如示例中的F7()函数),反之则最好共享(例如示例中的F1()函数)。按照此规则,针对图5的举例,例如可以如图6所示进行任务分割,将在两种等分点附近,代码长度相对来说较短,自身时间相对来说较长的函数F1()和F4()在处理器之间共享。需要注意的是,这里所说的代码长度和自身时间的长短不是绝对值,而是和各子函数之间的相对值。需要根据具体的应用和用户的实际需求和经验来具体制定。在具体的应用中,如果利用程序自动分割任务,当然也可以给出量化值。这都是本领域普通技术人员通过常规劳动可以得到的。
2.可以平衡考虑任务均衡的自适应性和指令/数据局部性对代码冗余比例的要求,选择适当的代码冗余比例。代码冗余的比例就是冗余的代码占全部代码的比例,比如1000行(汇编代码)的程序,处理器1和处理器2之间共享的子程序占100行,,处理器2和处理器3之间共享的子程序占200行,那么冗余比例就是(100+200)/1000=30%。共享更多的代码可以提供更好的自适应性。例如,虽然图2和图6所示的例子只是在处理器之间共享一个子函数(及其支路子函数),但是完全可以在处理器之间共享更多的子函数。但是,冗余比例越高,指令/数据局部性就越差。因此对于具体的应用而言,要平衡考虑任务均衡的自适应性和指令/数据局部性对代码冗余比例的要求。
3.可以将本地存储器/高速缓存大小考虑在内选择适当的代码冗余比例。如果在处理器之间共享代码过多,导致额外的高速缓存未命中(cache miss)或者内存不足,则会带来的其他性能问题。
4.从提高存储访问的局部性出发,可以规定某些子函数不适合在多个处理器之间“共享”。比如,这些子程序所访问的数据或指令空间非常有规律,甚至很固定。如果让这些子程序在多个处理器之间调度,会导致局部性明显恶化。
5.可以规定某个处理器处理的任务不与其他处理器共享。比如,这个处理器运行的子程序本身负载很稳定,基本上可以完全地利用处理器的计算资源,和别的处理器共享部分子程序反而会造成该处理器利用率抖动而降低利用率。
6.可以找出函数调用链中从各种标准来看更适合被分割的点,从而辅助上述任务分割。例如,在这些调用点上,调用方和被调用方之间的通信很少,实时性要求很低,也就是所谓的弱耦合。
显然,上述启发性规则只是举例。在实际应用当中,可以有根多的启发性规则。
上面已经充分描述了本发明的在多处理器环境中的流水线处理方法。下面描述本发明的在多处理器环境中的流水线处理设备。在下面的说明中,为简明起见,对于与前述流水线处理方法中相同或者类似的部分不再重复说明,针对流水线处理方法所说明的技术细节均可应用于流水线处理设备。此外,针对流水线处理设备所说明的具体技术细节亦可应用于流水线处理方法。
如图10所示,图示了根据本发明的一种优选实施方式的流水线处理设备1000。
该流水线处理设备1000包括分割装置1002、处理器状态确定装置1004以及动态调节装置1006。分割装置1002在分割子任务并分配给各处理器时,使各处理器处理的子任务有一定程度的交叠,即交叠部分的子任务由多个处理器“共享”。在任务执行的过程中,处理器状态确定装置1004确定各处理器的状态,据之动态地决定交叠部分由共享交叠的子任务的处理器中的哪一个来执行(下面称为子任务动态调节),从而实现多处理器之间的动态均衡。
分割装置1002进行的任务分割和和动态调节装置1006进行的子任务的动态调节的具体示例可以参见前文结合图2的说明。
如前所述,处理器状态的确定可以有多种方式。包括指令队列读取法(微观粒度),系统级的对应CPU的任务队列状态读取法(宏观粒度),在CPU里面设立一个可配置的计时器(Timer)和汇报模块主动提供该CPU或者该CPU核的状态,或者统计CPU的吞吐量和/或处理延时等。此外,也有许多现有的监视CPU负载的工具,例如无论是在视窗还是在Linux/unix操作系统中,都有监视CPU负载的工具。
如前所述,处理器状态的确定和子任务的动态调节可以通过将代码植入作为任务的应用中的方式来实现。也就是说,处理器状态确定装置1004和动态调节装置1006可以被集成到任务本身当中。
在一种优选实施方式中,对于处理器状态确定装置1004而言,其任务是比较独立的,因此其也可以在任务外部单独地实现为一个模块,包括现有的模块(比如上述操作系统CPU负载监视工具),而动态调节装置1006只需要从外部的处理器状态确定装置1004直接读取处理器的状态。
在一种优选实施方式中,除了代码植入之外,动态调节装置1006也可以在任务外部实现,例如通过前述的守护进程法。此时,类似于前一优选实施方式,处理器状态确定装置1004不仅可以与动态调节装置1006一起通过守护进程来实现,也可以作为单独的装置在守护进程之外实现,动态调节装置1006只需要从处理器状态确定装置1004直接读取处理器的状态即可。
分割装置1002可以用现有技术来实现,只不过,按照本发明,需要使被分配给各处理器的任务存在在执行时动态分配的交叠部分。这只需要简单地移动现有技术中每一部分子任务的开始点和结束点即可。
如前所述,分割装置1002对任务的分割一般是针对关键路径进行分割的,因此要基于对关键路径的认识。基于关键路径即可以使用传统的方法进行如图1所示或者如图2所示的任务分割。
对于一个具体应用来说,关键路径可以是已知的。例如,编程时保留了关键路径信息,或者在以前分析过关键路径,或者关键路径可以由外部工具提供。
当关键路径不已知而需要进行分析以获得关键路径时,首先需要有分析装置1008对应用程序进行概要分析(profiling),以获取应用的调用关系图(call-graph)。这一步有许多现有的工具可以使用,例如用Intel vTune(http://www.intel.com/cd/software/products/apac/zho/vtune/index.htm)、GNU gprof(http://www.gnu.org/software/binutils/manual/gprof-2.9.1/gprof.html)或者oprofile(http://oprofile.sourceforge.net/news/)等应用程序/代码分析工具,或者可以使用与上述工具类似的原理。
然后,可以由关键路径确定装置1010根据调用关系图确定数据处理的关键路径。关键路径的确定也有很多现有的工具可供使用。例如上文提到的概要分析工具,同时也可以确定关键路径。
这样,基于所确定的关键路径,分割装置1002即可进行任务的分割。在本发明中,为了使处理器的负载更加均衡,提出了关于任务分割的更为优选的实施方式,以便更加准确地均匀分割任务,并更加恰当地确定在处理器之间共享的子任务。
为此,可以将分析装置1008配置为找出关键路径中每一个函数的“自身时间(selftime)”,并可以将分割装置1002配置为按时间平均的原则对任务进行分割,将分界点上的例程由前后两个处理器共享。现有技术中也有很多工具可以分析得到函数的自身时间,例如前述概要分析工具。
作为“自身时间”的替代方案,可以利用“代码长度”(code length)进行任务分割。为此,可以将分析装置1008配置为找出关键路径中每一个函数的代码长度。并可以将分割装置1002配置为按代码长度平均的原则对任务进行分割,将分界点上的例程由前后两个处理器共享。同样,现有技术中也有很多工具可以分析得到函数的代码长度,例如前述概要分析工具。
根据一种优选的实施方式,可以将分析装置1008配置为同时确定每一个函数的自身时间和代码长度,并将分割装置1002配置为综合考虑自身时间和代码长度进行任务的分割。
根据本发明的更为优选的实施方式,还可以对分割装置1002应用一些启发式规则或者说约束条件。这些规则包括但不限于:
1.具有较大代码长度但是较短自身时间的例程最好不在处理器之间共享,反之则最好共享。
2.可以平衡考虑任务均衡的自适应性和指令/数据局部性对代码冗余比例的要求,选择适当的代码冗余比例。。
3.可以将本地存储器/高速缓存大小考虑在内选择适当的代码冗余比例。
4.从提高存储访问的局部性出发,可以规定某些子函数不适合在多个处理器之间“共享”。
5.可以规定某个处理器处理的任务不与其他处理器共享。
6.可以找出函数调用链中从各种标准来看更适合被分割的点,从而辅助上述任务分割。
显然,上述启发性规则只是举例。在实际应用当中,可以有根多的启发性规则。
如本领域的普通技术人员所能理解的,本发明的方法和装置的全部或者任何步骤或者部件,可以在任何计算设备(包括处理器、存储介质等)或者计算设备的网络中,以硬件、固件、软件或者它们的组合加以实现,这是本领域普通技术人员在了解本发明的内容的情况下运用他们的基本编程技能就能实现的,因此不需在此具体说明。
此外,显而易见的是,在上面的说明中涉及到可能的外部操作的时候,无疑要使用与任何计算设备相连的任何显示设备和任何输入设备、相应的接口和控制程序。总而言之,计算机、计算机系统或者计算机网络中的相关硬件、软件和实现本发明的前述方法中的各种操作的硬件、固件、软件或者它们的组合,即构成本发明的设备及其各组成部件。
因此,基于上述理解,本发明的目的还可以通过在任何信息处理设备上运行一个程序或者一组程序来实现。所述信息处理设备可以是公知的通用设备。因此,本发明的目的也可以仅仅通过提供包含实现所述方法或者设备的程序代码的程序产品来实现。也就是说,这样的程序产品也构成本发明,并且存储有这样的程序产品的存储介质也构成本发明。显然,所述存储介质可以是本领域技术人员已知的,或者将来所开发出来的任何类型的存储介质,因此也没有必要在此对各种存储介质一一列举。
在本发明的设备和方法中,显然,各部件或各步骤是可以分解和/或重新组合的。这些分解和/或重新组合应视为本发明的等效方案。
根据以上说明可知,本发明采用流水线模型来分割任务,并具有动态重新分配部分子任务的能力,从而在与子任务相关联的处理器之间自适应地平衡负载。这使得能够更好地利用处理资源。此外,由于缩短了代码路径(这是由于将较大的任务分解为较小的子任务),从而形成更好的指令局部性。这对于高速缓存小(例如小L2高速缓存,没有L3高速缓存)或者本地存储能力低的处理器(例如IBM的CELL处理器)是很重要的。当将本发明应用于网络数据处理例如DPI时,由于流水线模型的采用而能保持数据包处理的顺序,从而在避免了数据相关性问题的同时最佳地利用了并行资源,大大提高了效率。
以上结合本发明的优选实施方式对本发明进行了详细说明。本领域的普通技术人员知道,本发明不限于这里所图示和描述的细节,而可以在不脱离本发明的实质范围的前提下作出各种改进和修改,这些改进和修改都在本发明的保护范围之内。
尤其是,对于本领域普通技术人员来说显而易见的是,本发明不仅可应用于DPI应用,事实上可以应用于任何任务的子任务在多处理器之间的均衡,而无论所述任务是什么或者其处理的数据是什么。