内容概要

上一篇章里,我们已经学习了分数计算的基本概念,Solver通常会将大部分执行时间用于运行分数计算(在其最深层次的循环中调用)。在这个篇章里,我们来讲解一些分数计算的一些技巧,及一些陷阱。

分数计算速度

在求解出一个问题后,Solver将记录每秒的分数计算速度。这是测试分数计算速度的最好办法,尽管它受到一些非分数计算执行时间的影响。它取决于问题数据集的问题规模。

在调整分数计算时,要着重于最大限度地提高分数计算速度,而不是最大限度地提高最佳分数。有时对约束或者分数值做了很多的调整,但是有时结果会产生很少或没有最佳分数的改进,例如,当算法卡在局部或全局最优时。如果你关注的是计算速度而不是关注的最大化分数,分数计算的改进会更加明显。

此外,观察计算速度允许删除或增加分数约束,但是仍然与原来的计算速度进行比较。将最佳分数与原来的最佳分数进行比较是毫无意义的:这是在比较苹果和橘子,因为约束已发生变化,分数值也会有所变化。

实际上说了这么多,大家如果想提高计算分数时,方向是如何提高每一步的计算速度,而不是如何提高最大化分数。

增量分数计算

当求解器每次进行下一步的计算时,增量分数计算会计算与先前状态的增量,以找到新的分数,而不是在每次解决方案评估时重新计算整个分数。

例如,当单个皇后A从第1行走到第2行时,它不会去检查皇后B和C是否可以互相攻击,因为它们都没有变化。

incrementalScoreCalculationNQueens04_edit.png

Drools的分数计算极大的提高了分数计算速度且支持增量,而不需要我们写一个复杂的增量分数计算算法。只要让Drools的规则引擎来做事情。(后续我们将会学习Drools的使用)。

避免远程服务

不要在你的分数计算中调用远程服务,网络延迟会极大降低分数计算性能。如果可能的话,将这些远程服务的结果缓存起来。

如果一个约束条件的某些部分可以在求解器启动时计算一次,并且在求解过程中不会改变,那么就把它们作为Problem Fact放入Solution类的对象。

无意义的约束

如果知道某个约束条件永远不会被打破(或者它总是被打破),就不要为它写一个分数约束条件。例如,在n个皇后中,分数的计算并不检查是否有多个皇后占据了同一列,因为皇后的列永远不会改变,每个解决方案都是从每个皇后在不同的列开始的。

内置硬约束

有时可以不实现硬约束,而是将其内置。例如,如果课程A不应该被分配到X室,但是它在Solution类上使用了ValueRangeProvider,所以Solver经常会试图把它也分配到X室(只是发现它破坏了一个硬约束)。在PlanningEntity上使用ValueRangeProviderFilter来过滤课程A只应该被分配到一个与X不同的房间,而不能分配到其它房间。

这在某些用例中可以带来很好的性能提升,不仅仅是因为分数计算更快,主要是因为大多数优化算法会花更少的时间来评估不可行的解决方案。

其它技巧

  • 如果在做int值的总和,不要让Drools在double中求和,这样会花费更多时间。

  • 请使用服务器模式(Java-server),性能可以提高50%。

  • 请使用最新的Java版本。Java 1.5切换到1.6,性能提高了30%。

  • 记住一点,不要过早的优化。确保你的设计足够灵活,允许基于配置的调整。

分数陷阱

我们来看CloudBalance这个例子,如果蓝色进程从一个超载的计算机移动到一个空的计算机,那么硬分数应该会提高。但是这个分数的实现未能做到这一点。

scoreTrap_edit.png

Solver求解器在这样的分数设计上求解,会花费大量的时间计算,而结果往往很不理想(特别是如果过载的计算机上有更多的进程)。因为这样的设计,求解器可能一开始将更多的进程转移到那台超载的计算机上,因为这样做没有任何惩罚,因为无论超载多少都只扣除1个硬约束的分值。若每超载1个则扣除1个硬约束分值,则不会出现这样的情况。

处理分数陷阱的方法有几种:

  • 改进分数约束,在分数权重上做出区分。例如,对每一个缺失的CPU进行惩罚,而不是在缺失任何CPU的情况下只惩罚-1hard。

  • 用一个分数约束来做这种区分。例如,对每一个缺失的CPU惩罚-1subsoft,如果有任何CPU缺失,则在-1hard之上。该业务忽略了subsoft的分数等级。

stepLimit 基准测试

不同的分数约束其性能往往是不一样的。有时候,一个分数约束可以直接拉低整个分数计算的性能。使用Benchmarker做一分钟的运行,看看如果把所有的分数约束都注释掉,分数计算的速度会怎么样。

总结

这一篇章相对而言有些部分难以理解,大家在真正面对的不同的例子或者不同的约束设计是,会发现最难设计的地方是在于分值的设计.