Hi,
After searching the forum and online I’m now trying my luck here.
I’m having some performance issues using floating point numbers in rules.
I know computers are bad at floating point operations but what I’m seeing now feels ridiculous.
The following code takes about 2 minutes for openhab to refresh, and then 2.5 minutes to run.
During this time the processor load is over 100%.
val logID = "math.rules"
// val X = newArrayList(66f, 2, 5, 6)
// val Y = newArrayList(66f, 2, 5, 6)
// X.get(1)
val float Y2 = 17f
val float Y1 = 1f
val float X2 = 100f
val float X1 = 0f
var float Xtrigger = 3.4f
var float y
var float oldAqi = -100f
var int setPoint = 300
// --------------- air quality changed ---------------
rule "air quality changed"
when
// Item aqiHome changed
Item Test_switch_4 received command ON
then
logInfo(logID, "button pushed")
val newAqi = (aqiHome.state as DecimalType).floatValue
if (Math::abs(newAqi-oldAqi) >= Xtrigger) {
logInfo(logID, "aqi changed more than: " + Xtrigger)
y = ((Y2-Y1)/(X2-X1)) * newAqi + Y2 - ((Y2-Y1)/(X2-X1)) * X2
// setPoint = Math::round(((Y2-Y1)/(X2-X1))*newAqi + Y2 - ((Y2-Y1)/(X2-X1))*X2)
setPoint = Math::round(y)
// setPoint = Math::round(newAqi)
// purifier_1_favoritelevel.sendCommand(setPoint)
logInfo(logID, "purifier setPoint: " + setPoint)
}
end
Without that it refreshes and runs in 2-3 seconds.
As i understood it the Zulu JVM shipped with openhabian utilizes hard float so the performance penalty for using floats should not be so severe.
Does anyone have any ideas?
Sincerely,
Carl
PS.
Adding the output of: java -XshowSettings:properties -version
for reference
It is recommend not to use primitives (int, float…) in the rules script
Use Number type instead
val logID = "math.rules"
// val X = newArrayList(66f, 2, 5, 6)
// val Y = newArrayList(66f, 2, 5, 6)
// X.get(1)
val Number Y2 = 17
val Number Y1 = 1
val Number X2 = 100
val Number X1 = 0
var Number Xtrigger = 3.4
var Number y
var Number oldAqi = -100
var Number setPoint = 300
// --------------- air quality changed ---------------
rule "air quality changed"
when
// Item aqiHome changed
Item Test_switch_4 received command ON
then
logInfo(logID, "button pushed")
val newAqi = aqiHome.state as Number
if (Math::abs(newAqi-oldAqi) >= Xtrigger) {
logInfo(logID, "aqi changed more than: " + Xtrigger)
y = ((Y2-Y1)/(X2-X1)) * newAqi + Y2 - ((Y2-Y1)/(X2-X1)) * X2
// setPoint = Math::round(((Y2-Y1)/(X2-X1))*newAqi + Y2 - ((Y2-Y1)/(X2-X1))*X2)
setPoint = Math::round(y)
// setPoint = Math::round(newAqi)
// purifier_1_favoritelevel.sendCommand(setPoint)
logInfo(logID, "purifier setPoint: " + setPoint.toString)
}
end
I see, I assumed I had to use DecinalType since the xtend docummentation says “There are only decimal floating-point literals.”. I guess I don’t understand that sentence fully
Thank you for the code suggestion, running it however gives me this error.
2019-01-10 19:36:35.327 [ERROR] [ntime.internal.engine.RuleEngineImpl] - Rule 'air quality changed': An error occurred during the script execution: Could not invoke method: java.lang.Math.abs(int) on instance: null
I checked newAqi which is the only thing not declared in the rule and it definately has a state.
It’s a known problem with no known solution in site. For some reason (I have unsubstantiated theories) the Rules parser has a really hard time parsing .rules files that use primitives.
Luckily you almost never have to use primitives.
The problem isn’t in actually performing the operation. The problem is with the parser. My theory of what is going on is that Xtend, as a weakly typed language, prefers to put off until run time type checking. But when you use primitives it cannot put off the type checking so it has to do a whole lot more work to verify that all the types check out at parse time.
By default without forcing it, Xtend will use the BigDecimal class to represent all Numbers. By forcing your variables to be primitives you are bypassing this.
So, there are some places where a primitive must be used. These are usually in calls to library methods like abs. You can use a primitive in just these places though. For example:
Math::abs((newAqi-oldAqi).intValue)
For one or two calls to a library like this I see no reason to avoid the calls to intValue or doubleValue where needed. That shouldn’t impact performance too much. Typically there will be only one or two such calls in a given Rule anyway.