由于长时间的分数计算,提前终止基准测试

Terminate a benchmark early because of long score calculation

我想要达到的目标

目前,我 运行 在我的 OptaPlanner 项目中投入大量资金,并且随着当前实施的约束条件,他们甚至需要很长时间才能计算出初始分数。因此,给定的求解器会破坏整个基准测试,因为它卡住了并且无法终止。作为分数计算类型,我正在使用 Drools。

我正在尝试实现求解器的提前终止,该求解器在一定时间后仍未通过初始分数计算(不显示 "Solving started")。因此,在单个基准测试中,我想 运行 多个不同的输入,并且对于每个输入,我都希望有一个给定的计时器,如果该计时器在初始分数计算完成之前到期,我希望求解器立即终止。一个理想的选择是完成分数计算的百分比。

我之所以不直接着手进行优化,是因为我想有一个比较基线,并随着优化的进行跟踪结果。所以初始分数计算通过多少百分比的信息对我来说很重要。

我have/know目前

  1. 我使用的OptaPlanner版本是GitHub的版本,整个源代码是开放的(它不是网站上的官方版本,它是用JAR编译的,核心不是可编辑)
  2. 为基准的每个求解器实现计时器,在给定时间段后调用 solver.terminateEarly() 方法。
  3. 每个求解器 运行 在其唯一线程上。所以关系求解器:线程是 1:1。我找出哪个求解器当前正在执行代码的方法是在 Map<Integer,Solver> solverMap 中进行查找,其中键是执行求解器的线程的 hashCode 值 -> Thread.currentThread().hashCode()。随着求解器的开始和完成,此地图正在更新。这样我就可以从所有地方进行查找(optaplanner-examples、optaplanner-core、optaplanner-benchmark 项目和 Drools 规则(下面的示例))
  4. 从 Drools 文档中找到 kcontext.getKieRuntime().halt() 用于立即终止规则执行。
  5. 实施专门规则,在每次更改 planning/shadow 实体后到达 then 部分,然后从 then 部分首先检查求解器是否提前终止(由相应的计时器),如果它调用 kcontext.getKieRuntime().halt()。例如:

在下面的规则中,每次更改 ShiftAssignment 实例后都会到达 then 部分,如果求解器设置为提前终止,则规则执行将停止。

salience 1 //so that it is triggered first
rule "ShiftAssignmentChange"
    when 
        ShiftAssignment()   
    then 
    if(TerminateBenchmarkEarly.solverMap.get(Thread.currentThread().hashCode()).isTerminateEarly()){
        kcontext.getKieRuntime().halt();//This command is used to terminate the fire loop early.    
    }   
end

这些规则的目的是它们 salience 1 与默认选项 0 相对,因此它们将是第一个被执行的规则,规则执行将立即停止 6. kieSession.fireAllRules()org.optaplanner.core.impl.score.director.drools.DroolsScoreDirector calculateScore 方法调用 returns 执行的规则数。我可以使用这个度量作为初始分数有多少的基准 achieved.As 优化继续进行,预计这个数字会越来越高,所花费的时间会越来越少。

我目前面临的问题

我遇到的问题是,即使再次实现此功能,也需要花费大量时间才能检查规则,或者在某些情况下会因为 OutOfMemory 错误而崩溃。打开 Drools 的 Trace 选项,我能够看到它在一小部分时间将事实插入工作内存,然后它不断地输出 TRACE BetaNode stagedInsertWasEmpty=false。问题出在org.optaplanner.core.impl.score.director.drools.DroolsScoreDirector calculateScore方法的kieSession.fireAllRules()调用,fireAllRules的代码来自Drools核心,此代码被编译成JAR,因此无法编辑。

结论

无论如何,我知道这在某种程度上是一种黑客攻击,但正如我上面所说,我需要此信息作为基准,以了解我当前的解决方案在哪里,并在优化过程中跟踪基准信息。 如果有不同的(更聪明的)方法可以实现这一点,我会很乐意去做。

基准测试结果

Input 1

  • Entity count: 12,870
  • Variable count: 7,515
  • Maximum value count: 21
  • Problem scale: 22,068
  • Memory usage after loading the inputSolution (before creating the Solver): 44,830,840 bytes on average.
  • Average score calculation speed after Construction Heuristic = 1965/sec
  • Average score calculation speed after Local Search = 1165/sec
  • Average score calculation speed after Solver is finished = 1177/sec

Input 2

  • Entity count: 17,559
  • Variable count: 7,515
  • Maximum value count: 8
  • Problem scale: 21,474
  • Memory usage after loading the inputSolution (before creating the Solver): 5,964,200 bytes on average.
  • Average score calculation speed after Construction Heuristic = 1048/sec
  • Average score calculation speed after Local Search = 1075/sec
  • Average score calculation speed after Solver is finished = 1075/sec

Input 3

  • Entity count: 34,311
  • Variable count: 14,751
  • Maximum value count: 8
  • Problem scale: 43,358
  • Memory usage after loading the inputSolution (before creating the Solver): 43,178,536 bytes on average.
  • Average score calculation speed after Construction Heuristic = 1134/sec
  • Average score calculation speed after Local Search = 450/sec
  • Average score calculation speed after Solver is finished = 452/sec

Input 4

  • Entity count: 175,590
  • Variable count: 75,150
  • Maximum value count: 11
  • Problem scale: 240,390
  • Memory usage after loading the inputSolution (before creating the Solver): 36,089,240 bytes on average.
  • Average score calculation speed after Construction Heuristic = 739/sec
  • Average score calculation speed after Local Search = 115/sec
  • Average score calculation speed after Solver is finished = 123/sec

Input 5

  • Entity count: 231,000
  • Variable count: 91,800
  • Maximum value count: 31
  • Problem scale: 360,150
  • Memory usage after loading the inputSolution (before creating the Solver): 136,651,744 bytes on average.
  • Average score calculation speed after Construction Heuristic = 142/sec
  • Average score calculation speed after Local Search = 11/sec
  • Average score calculation speed after Solver is finished = 26/sec

Input 6

  • Entity count: 770,000
  • Variable count: 306,000 '
  • Maximum value count: 51
  • Problem scale: 1,370,500
  • Memory usage after loading the inputSolution (before creating the Solver): 114,488,056 bytes on average.
  • Average score calculation speed after Construction Heuristic = 33/sec
  • Average score calculation speed after Local Search = 1/sec
  • Average score calculation speed after Solver is finished = 17/sec

When commenting out the rules in Drools i get the next average score calculation speed (for Input 6):

  • After Construction Heuristic = 17800/sec
  • After Local Search = 22557/sec
  • After Solver is finished = 21690/sec

如果可能的话,我会首先专注于使 DRL 更快,而不是这些技巧。所以这归结为弄清楚哪些评分规则很慢。使用分数计算速度(在最后一个 INFO 日志行中)通过注释掉分数规则并查看它们对分数计算速度的影响来确定。

也就是说,通常我会建议查看 unimprovedSecondsSpentLimit 或自定义 Termination - 但这确实无济于事,因为在计算初始分数时不会检查这些内容从头开始:它们只在每次移动之间检查(所以在每个 fireAllRules() 之间,通常是 10k/sec)。