Child device parse method performance

First I want to say, this is a very typical amount of time needed, I see the same +- about 10% under normal system load.
This is the code:

logging("Before getChildDevice()", 100)
cd = getChildDevice("$$deviceTypeId")
if(cd != null) {
    logging("Before Child parse()", 100)
    // It takes 30 to 40ms to just call into the child device parse
    logging("After Child parse()", 100)

Child parse starts with writing "Child: parse()" to the log, and at the end "Child: END parse()". parse in the child is declared as such:
void parse(List<Map> description)

It takes about 40ms to enter cd.parse() and about 20ms to leave. Considering that I have all other code around this (before and after in the parent parse) run in a total of 40ms (parsing a LOT of data, I can probably optimize it down to 30ms with some additional tweaking), I find the total of about 60ms spent entering and leaving the child parse method is a lot, is there a way to optimize this? Can I do something differently? I know this is nit-picking and chasing ms is almost silly, but I want it to be efficient for the sake of efficiency and that fact this is code called very often.

Maybe if someone knows what ACTUALLY happens when a child method is called, this can be improved upon. Is metadata called every time? Could the content of metadata slow things down? Anything else that can slow things down on the way to parse or other methods in the child?

I don't know how interesting of a topic this is for others, but these things do interest me, and hopefully someone else who knows more than me also have an interest?

Making your code efficient without intimate measurement (i.e., actual processor results) is going to be very frustrating. Using the log has a (relatively) large error in the process since it calls a separate method in your code as well as a Hubitat routine (Log).

My objective is reducing processing steps and selecting the most efficient way for Hubitat to store the data. It would be nice to know the hub's overhead involved (comparative) between the following:

  • device.currentValue("param") vs state.param
  • sendEvent(name; "param", value: value) vs state.param = value
  • state.param vs getDataValue("param")
  • calling the setting param vs getDataValue("param"), device currentValue("param"), or state.param.


While your question still stands, some of them boil down to this issue: state is not the same as a device attribute, which is what sendEvent creates an event to (possibly) modify. Specifically, state is not more or less not intended to be accessible to apps (except perhaps a parent app if that device is a child of one) or other devices (just the own device as a way to persist data between executions). Attributes are intended to be used by apps/users. If there is no reason something would need to be usable in that manner (and in general, if it's not part of a standard capability or a custom attribute you think there's a good reason someone would want to use in an app/automation), then sendEvent (or createEvent) is probably not appropriate.

Basically, I wouldn't use performance as a consideration here--but if you did, it's quite likely that state would fare better in most respects considering that it doesn't save history to the database (or wake subscribed apps, etc.) like it does for attributes.

Yes, absolutely, but when the time spent is this much different it is possible to identify hot spots.

That is for sure important, I do still find it hard to truly know how everything will be executed, and what will take more time to run, since certain things in Groovy which may seem simple, would actually be a lot of code if decompiled into java bytecode.

If you use only one log I would agree, especially for small time differences, but an operation that is non-variable in nature will execute in more or less the same amount of time. Operations which take a very long time compared to others, ore most definitely noticeable. Since I don't have unit tests and code profiling available to me, this is a rough method of finding "hot spots". Eventually I will probably move to use unit tests (someone has started on this I've seen) and profiling of the code in my own stubbed sandbox, but I need both more time and a better understanding of the inner workings of HE before I can attempt that.

It is also not written to the database until the current thread is about to exit as I've understood it, so if you set it multiple times in the same thread, it will only be the last one that is ever written. Don't quote me on this, but this is what I seem to be seeing, there is probably some clear information about this somewhere in this forum, I've just not read it yet.

My understanding is that anytime you call a child device/app from a parent, the hub has to basically prep the device/app object if it were being called by an event/scheduler,etc. In otherwords, you're not simply calling parse() on the child device.. The hub has to instantiate the device object which includes loading the devices state from the database.

There are all different tools for different purposes..

currentValue retreives the current value of and attribute.
sendEvent updates the value of an attribute. It also fires off events for other listeners.
state.param this is private data for an app or driver to use. It is not available to other apps/drivers.
getDataValue is a string-only data store that is publically accessible by any app that selects the device.

I don't think there's any significant performance difference between these...

However, if you are looking to send events to devices from a parent app, there is a little performance-boosting trick you can use..

sendEvent(deviceNetworkId, [name: "test", value: "123"])

This avoids having to load the object since no child object methods are are being called. You're just calling SendEvent with a DNI. I've benchmarked that as being several milliseconds quicker.


That is what I suspected considering the time spent, so what would be the most efficient way to get an event into a child device? Any way of doing like you can from an app? I've not even tried since I wanted to be compatible how the built-in Composit devices work, but maybe there is a way?

Is it really every time? If I keep the device object around for multiple different calls in the same method, would I have the same penalty every time? I've not tried to benchmark this yet, but I will, this would be good to know.

From a parent to a child, you have no choice but to use child.sendEvent().
But from a parent App, you can use sendEvent(deviceNetworkId, ...).

It is once if those calls are all happening at some point during the execution of your method
For instance, if you call device.sendEvent three times in the parent device's parse() method, then the device only has to be loaded once. But for each call to parse() the device object has to be loaded. Which to be expected since there's an expectation that for example, state is up-to-date from other calls that might have been occurring concurrently.

That is what I thought, I couldn't find anything, thank you for confirming!

This makes sense, I'll do some testing and think about how I can make it more efficient... Thank you for clearing this up! This way I didn't have to experiment blindly. Still need more time on the platform.

1 Like

so this

if (respstatus == 200){
        	if (state.errorCount != 0) {
				state.errorCount = 0
        	if (state.currentError != null) {
				state.currentError = null
        	currentDevices = response?.data?.data
	        currentDevices.each {
        		//log.debug "it ${it?.name} - ${it?.device_id}"
				def isChild = getChildDevice(it?.device_id)
            	if (isChild) {
        "Sending status of '${it?.status_open[0]}' to child '${it?.name}'" 

the isChild.datain (def data in is the driver to recie this) could be replaced with?

def OpCl = (it?.status_open[0] == 'device_status.dc_close') "closed"   ?: "open"
sendEvent(isChild, name: "contact", value: OpCl).

In an app, yes, I've not tried this, but if my understanding from the above is right, yes, you could do this, you could even skip getting the child device and just pass the device id directly to sendEvent.

From the docs:

void sendEvent(DeviceWrapper device, Map properties)
void sendEvent(String dni, Map properties)

ok im missing something hear why is the def OpCl coming up as null? "Sending status of '${it?.status_open[0]}' to child '${it?.name}'" 
                    log.debug "${it?.status_open[0]}"
                    if (it?.status_open[0] == "device_status.dc_close") { 
                        def OpCl = "closed"
                    else {
                        def OpCl = "open"
                    log.debug "$isChild $OpCl"
                    //sendEvent(isChild, name: "contact", value: OpCl)

Do the def outside the if.

fixed it it needed [ ] round the map
sendEvent(isChild, [name: "contact", value: OpCl])


currentDevices.each {
        		//log.debug "it ${it?.name} - ${it?.device_id}"
				def isChild = getChildDevice(it?.device_id)

            	if (isChild) {
                	// "Sending status of '${it?.status_open[0]}' to child '${it?.name}'" 
                    //log.debug "${it?.status_open[0]}"
                    def OpCl = ""
                    if (it?.status_open[0] == "device_status.dc_close") { 
                        OpCl = "closed"
                    else {
                        OpCl = "open"
                    //log.debug "$isChild,  ${it?.device_id},  $OpCl"
                    sendEvent(isChild, [name: "contact", value: OpCl])

Yes, named arguments in Groovy are collected in a map and placed as the first argument in a method call, which is why you would get a no signature match error writing it without []. The sendEvent call takes the map as the second element, not the first.