由于长时间的分数计算,提前终止基准测试
Terminate a benchmark early because of long score calculation
我想要达到的目标
目前,我 运行 在我的 OptaPlanner 项目中投入大量资金,并且随着当前实施的约束条件,他们甚至需要很长时间才能计算出初始分数。因此,给定的求解器会破坏整个基准测试,因为它卡住了并且无法终止。作为分数计算类型,我正在使用 Drools。
我正在尝试实现求解器的提前终止,该求解器在一定时间后仍未通过初始分数计算(不显示 "Solving started")。因此,在单个基准测试中,我想 运行 多个不同的输入,并且对于每个输入,我都希望有一个给定的计时器,如果该计时器在初始分数计算完成之前到期,我希望求解器立即终止。一个理想的选择是完成分数计算的百分比。
我之所以不直接着手进行优化,是因为我想有一个比较基线,并随着优化的进行跟踪结果。所以初始分数计算通过多少百分比的信息对我来说很重要。
我have/know目前
- 我使用的OptaPlanner版本是GitHub的版本,整个源代码是开放的(它不是网站上的官方版本,它是用JAR编译的,核心不是可编辑)
- 为基准的每个求解器实现计时器,在给定时间段后调用
solver.terminateEarly()
方法。
- 每个求解器 运行 在其唯一线程上。所以关系求解器:线程是 1:1。我找出哪个求解器当前正在执行代码的方法是在
Map<Integer,Solver> solverMap
中进行查找,其中键是执行求解器的线程的 hashCode 值 -> Thread.currentThread().hashCode()
。随着求解器的开始和完成,此地图正在更新。这样我就可以从所有地方进行查找(optaplanner-examples、optaplanner-core、optaplanner-benchmark 项目和 Drools 规则(下面的示例))
- 从 Drools 文档中找到
kcontext.getKieRuntime().halt()
用于立即终止规则执行。
- 实施专门规则,在每次更改 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)。
我想要达到的目标
目前,我 运行 在我的 OptaPlanner 项目中投入大量资金,并且随着当前实施的约束条件,他们甚至需要很长时间才能计算出初始分数。因此,给定的求解器会破坏整个基准测试,因为它卡住了并且无法终止。作为分数计算类型,我正在使用 Drools。
我正在尝试实现求解器的提前终止,该求解器在一定时间后仍未通过初始分数计算(不显示 "Solving started")。因此,在单个基准测试中,我想 运行 多个不同的输入,并且对于每个输入,我都希望有一个给定的计时器,如果该计时器在初始分数计算完成之前到期,我希望求解器立即终止。一个理想的选择是完成分数计算的百分比。
我之所以不直接着手进行优化,是因为我想有一个比较基线,并随着优化的进行跟踪结果。所以初始分数计算通过多少百分比的信息对我来说很重要。
我have/know目前
- 我使用的OptaPlanner版本是GitHub的版本,整个源代码是开放的(它不是网站上的官方版本,它是用JAR编译的,核心不是可编辑)
- 为基准的每个求解器实现计时器,在给定时间段后调用
solver.terminateEarly()
方法。 - 每个求解器 运行 在其唯一线程上。所以关系求解器:线程是 1:1。我找出哪个求解器当前正在执行代码的方法是在
Map<Integer,Solver> solverMap
中进行查找,其中键是执行求解器的线程的 hashCode 值 ->Thread.currentThread().hashCode()
。随着求解器的开始和完成,此地图正在更新。这样我就可以从所有地方进行查找(optaplanner-examples、optaplanner-core、optaplanner-benchmark 项目和 Drools 规则(下面的示例)) - 从 Drools 文档中找到
kcontext.getKieRuntime().halt()
用于立即终止规则执行。 - 实施专门规则,在每次更改 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)。