Built-in drivers and apps might have the same problem. Since their code is not open, only staff could confirm, @mike.maxwell @gopher.ny @bobbyD
This is how drivers and apps are normally implementing logging
logDebug "current state.deviceProfile=${state.deviceProfile}, settings.forcedProfile=${settings?.forcedProfile}, getProfileKey()=${getProfileKey(settings?.forcedProfile)}"
void logDebug(String msg) {
if (settings?.logEnable) {
log.debug "${device.displayName} ${msg}"
}
}
This might look ok at a first glance, but there is a performance penalty with this approach.
When logDebug
is invoked with the dynamic String parameter, there are several CPU costly operations required to create the final String, that will be then passed onto the method as a calculated static string (new object).
String concatenation per se is already a relatively costly operation for CPU and memory.
In the example above, there are multiple interpolation operations, some requiring calls to other methods that will calculate the concatenation values. Method invocation is also a CPU costly operation, not to mention what they do inside them.
EDIT: Besides the initial CPU usage, a string with 4 interpolations like the one above, results in 5 new String objects allocated in memory, those will later require some more CPU work from the Garbage Collector to be cleaned.
What are all these CPU cycles and memory used for?
Most of the time: NOTHING!
Since the debug will be disabled, the process/thread won't get inside the if
statement!
So all the calculations and interpolations will create a final string that most of the times IS NOT USED AT ALL.
This might sound unimportant, but we must remember that the hub's CPU is not as powerful as a PC or Smartphone. Even if it was, we'd still be wasting some of it's power for nothing!
Also when you have hundreds of devices and apps, this adds up and might become a relevant performance penalty.
So, how would it be the proper way to implement logs then?
I do it this way, if you know another, please share!
Add an if
statement before each log method invocation, at least for debug and trace (if available). Info, warning and error would normally be logged anyway and don't even have a toggle, so they don't need the if
.
if (logEnable) logDebug "${costly.operation.here}"
EDIT: @JustinL proposed some interesting alternatives below, closures seem to be the sweet spot. No if
statement needed, and the message is only interpolated when it will actually be used.
BTW, I'm talking about logs here, but of course, this is a programming best practice that can be applied anywhere were there is a similar mechanism.
Also, there is another problem that affects logging and most of the times all strings in a driver/app: The indiscriminate use of double quotes!
EDIT: There is no need for this, as @bertabcd1234 tested, the compiler does that optimization already.
In Groovy, single-quoted strings are treated as static strings and do not support variable interpolation, making them slightly more efficient for simple strings without variables. Using single quotes can help avoid unnecessary parsing overhead associated with double-quoted strings, which do allow for interpolation and escape sequences.
So, double quotes should be used only where necessary, do not double quote static strings.