Function Node, which code is more efficient?

In a function node which code construct would be more efficient?

Straightforward:

var inside = global.get('insidePolygon');

msg.InAlertRegion=true;   // default is to accept detection anywhere in image

var poly=[[]];

// polygon points picked from image using GIMP
var DriveWayPoly = [ [600,1400], [2200,700], [3841,500], [3841,1500], [2200, 2161], [1400,2161] ];
var CliffwoodPoly =[ [-1,1300], [3841,1300], [3841,2161], [-1,2161] ];
var IntersectionPoly =[ [-1,850], [400,850], [700,1081], [-1,1081] ];
var HummingbirdRightPoly =[ [-1,1100], [1750,950], [1650,1200], [2900,1200], [3841,1800], [2800,2161], [-1, 2161] ];
var FrontDoorPoly =[[-1,900], [600,1100], [600,900], [3841,1100], [3841,2161], [-1,2161], ];
var HummingbirdLeftPoly =[[1200,850], [500,1600], [-1,2161], [3841,2161], [3841,1000], [3000,1000]];

if(msg.filename.includes("DriveWay"))
    poly=DriveWayPoly;
else if(msg.filename.includes("Cliffwood"))
    poly=CliffwoodPoly;
else if(msg.filename.includes("Intersection"))
    poly=IntersectionPoly;
else if(msg.filename.includes("HummingbirdRight"))
    poly=HummingbirdRightPoly;
else if(msg.filename.includes("FrontDoor"))
    poly=FrontDoorPoly;
else if(msg.filename.includes("HummingbirdLeft"))
    poly=HummingbirdLeftPoly;
   
    
if(poly.length > 2)
    msg.InAlertRegion = inside([msg.endX, msg.endY], poly);  // returns false if point not inside polygon

if(msg.InAlertRegion === true)
    msg.filename=msg.filename.replace("AI", "AI.alert");
return msg;

Or this to skip the creation/initialization of probably unused static variables:

var inside = global.get('insidePolygon');

msg.InAlertRegion=true;   // default is to accept detection anywhere in image

var poly=[[]];

if(msg.filename.includes("DriveWay"))
    poly=[ [600,1400], [2200,700], [3841,500], [3841,1500], [2200, 2161], [1400,2161] ];
else if(msg.filename.includes("Cliffwood"))
    poly=[ [-1,1300], [3841,1300], [3841,2161], [-1,2161] ];
else if(msg.filename.includes("Intersection"))
    poly=[ [-1,850], [400,850], [700,1081], [-1,1081] ];
else if(msg.filename.includes("HummingbirdRight"))
    poly=[ [-1,1100], [1750,950], [1650,1200], [2900,1200], [3841,1800], [2800,2161], [-1, 2161] ];
else if(msg.filename.includes("FrontDoor"))
    poly=[[-1,900], [600,1100], [600,900], [3841,1100], [3841,2161], [-1,2161], ];
else if(msg.filename.includes("HummingbirdLeft"))
    poly=[[1200,850], [500,1600], [-1,2161], [3841,2161], [3841,1000], [3000,1000]];
   
    
if(poly.length > 2)
    msg.InAlertRegion = inside([msg.endX, msg.endY], poly); // returns false if point not inside polygon

if(msg.InAlertRegion === true)
    msg.filename=msg.filename.replace("AI", "AI.alert");
return msg;

I like the second form better since its fewer lines of code by losing the "intermediate" variables, but I think the first is a bit easier to understand.

Or won't it matter? Expecting to run this on Pi3/4 class machines.

Ultimately on flow startup I'd like to read the variable values from a file and store them in flow context for use in the function, this would then seem to require the re-introduction of the intermediate variables.

At that level, you'd need a deep working knowledge of how the V8 runtime optimises code before it gets executed.

I doubt you'd be able to measure the difference so it ultimately comes to personal preference.

I guess this is the solution, unless someone else here has such understanding.

Not unexpected as the answer, If the overhead of instancing and initializing a potentially unused variable is significantly higher than an un-executed if statement the second form should be measurably better on weak hardware, but if Deploy is some kind of Just In Time compilation then its unlikely to matter.

Then the question is if it is more efficient to use a switch statement instead of all those if/else if statements. At least I think readability would be improved if you use a switch.
Next is to think about probability. Where is it most likely you will make detections? Is it on the "Driveway" or "Intersection"? The higher probability of detection, the earlier in the code so you don't have to check statements unnecessary (maybe the code should be adaptive in that sense, learning by doing, a bit of micro AI)

2 Likes

I'd agree with this, and if the compiler / JIT is any good it will be just as efficient. [snark]Javascrpt!? Efficient?!?![/snark]

Another option might be to use a table to store your polys, loading it from a file. I do something similar for a lookup for all my MQTT-connected nodes, and it's much easier to read. It also means I can change the contents of the file more easily just using a text editor.

Although, unless it's something that's happening many times a second, I don't think you'll notice any small performance differences between solutions.

Or perhaps even better without if and switch (and expandable)

var polys={“poly01”:[1,2,3,4,5],
 “poly02”:[0,8,15]};

var getMeThis=“poly02”;

node.warn(polys[getMeThis]);

and let JavaScript do the magic.

2 Likes

Good info, I'm a JavaScript newbie.

As to the "JavaScript magic" @Christian-Me offers, looks to be using some kind of json array access method but poly has to be a 2D array size N by 2 of the x,y coordinates of the vertex points, with N >=3, it seem oblivious how to extend it, but I don't see how its possible to avoid the switch or nested if else statements on the specific camera to access the appropriate polygon. So this would be a trick I'd be excited to learn about!

So far i haven't encountered the switch statement in the JavaScript tutorials I've use so I was thinking it might be missing.

My "efficiency" questions stem from inexperience with JavaScript and experience with other systems like MatLab/Octave where appending or growing an array is so expensive you are far better pre-allocating a large enough one and possibly wasting some storage. Before I retired, I was often a "hero" by taking other colleagues MatLab code and speeding it up by 10X or more with proper vectorization.

I'm with @krambriw that a switch is much more readable and maintainable than nested if else. In C at least, its much more efficient than nested if else statements, but with javascript's string indexing I could see how there might not be a switch construct. I kind of have them ordered by traffic density of out of polygon detection, but since I live on a corner and traffic is bidirectional, its gonna be wrong about half the time :slight_smile:

Now that I've got the basic functionality working, I'm looking to learn to improve readability/maintainability and/or efficiency. Conversing with experts is far more efficient than stumbling through documentation.

As an aside, the "intersection" camera polygon rejects almost every detection, but I added it after witnessing a few close calls from speeding cars and dog walkers, all detection images are saved, but I'm not usually notified since almost none are of interest. But if there is an accident I can quickly access the 24/7 video record using the detection timestamp -- I've been appalled by the poor "scrubbing" capability of the consumer grade security DVRs I've had access to. If "something happened" two days ago I can in a few minutes "slide show" the detection images from the appropriate cameras and quickly find the time of the event to use to access the 24/7 recording.

Off to learn a bit about javascript switch statement.

I suspect with the evolutionary history of JavaScript and nodejs that Google searching for JavaScript construct efficiency would get me a lot of information that is no longer true -- the "promises" stuff so far confuses me greatly.

@molesworth, actually it is trying to execute many times per second potentially 45 times/second, which it does on my desktop development system, but the i7-4500U "fanless Mini PC" 24/7 dedicated system it can only do about 39 fps, so any efficiency improvements would be welcome.

Then we have development efficiency vs execution efficiency. Would the extra time used to develop in C/C++ ever be made up in execution time compared to using Python and node-red? Except for high production volume systems the answer is almost always NO.

After a quick look at the JavaScript switch statement, seems its pretty much the same as in C except the expression can evaluate to any object type and targets are === compared to the case targets. This should compile to a variation of a jump table and be "more efficient" than an if-else sieve.

But I'm "switching" on camera names which are variable and embedded in the msg.filename
property with: msg.property.includes("cameraName")

I don't see any real alternative to the if-else sieve without a lot of "upstream" changes that won't be of any other benefit. Especially since at this point unless it improves the AI performance I want to keep the Python code unchanged.

For my code I've put the camera names in the Python code. My GitHub version has generic Cam1, Cam2, ... names, so I can sub-string the camera number from the msg.filename and then use it as the switch target.

I think this is the way to go for my generic GitHub version as my next "improvement" was to be mapping camera numbers to locally useful names in node-red. In this case I think it might aid efficiency and improve readability for easier modifications to do the "In Region" filtering with a switch structure.

Keeping localization in node-red as much as possible is good as it takes a long time for the rtsp streams to become active (~6 seconds per camera) but changing the node-red and redeploy can be done with the python still running saving a lot of development time. With Onvif cameras changing the python code vs the node-red is pretty much a toss-up unless you don't know much Python.

Another @molesworth: Putting the active region polygons and a fixed object bogus detection locations in files to be read on deploy is on the TODO list, but towards the bottom for now. Using the node-red filter node built-in editor is I think easier to explain to someone who is not a computer geek. I have a couple of systems running at friends locations and they've been able to tweak the node-red for their needs (like add a time of day on/off schedule) whereas editing the Python was off the table, and even creating the camera URL files was something I basically had to do for them.

1 Like

It sounds like you're getting there, and it's an interesting project so good luck. I'm sure you'll have lots more questions as you progress.

And I'm sure you'll get to "geek" status as well. We've all learned as we went along, just some of us have had a bit longer at it :wink:

1 Like

One thing I noticed after I read your first post again is how different the data is handled each time you call the function

  1. You define all data in variables, process it and send out a fraction of the data, then throw everything away and do some clean up.
  2. Do the processing first and then send/load only the useful data
  3. (Mine) same as 1 but with a perhaps more efficient decision making . And by the way it doesn’t matter how complex your data is or mixed (objects and arrays) - the 2D array was only an example it could be your array or structure too.
  4. (New) think about to store all the data before (assuming it is most of the time constant) in context as an object or array (in this case the same). A context/flow/global.get do not copy the data into the function, it only provides a link/pointer to the original position. If you then find a way to implement the logic without moving/loading the data around it could give you a significant boost. The data should be organized in that way that it could be selected easy and fast. Even recursive crawls through branches of data can be done fast when you keep in mind only passing pointers and not actual data as long as possible. And let JavaScript do the work because as far as I know code behind the build in functions and methods are highly optimized.

Another one: try to avoid all the string.include() and prepare the incoming data once at the beginning if possible instead searching through msg.filename in every if / switch statement.

Certainly there is more optimizations possible but JavaScript does a good job.

Perhaps measure the time:

var timestamp = Date.now();

// code here 

node.warn(Date.now()-timestamp +”ms”);

Unless you are calling this thousands, or at least hundreds, of times a second then don't worry about the efficiency. Concentrate on keeping the code as simple and as easy to understand as possible. That way you will minimise the likelihood of bugs and make maintenance of the code more easy in the future. Both of those are much more important than saving a small amount of processor time.
A couple of small points, if you are defining variables whose value never changes then you should use const so that the interpreter knows that they will not change. Also you should generally use let rather than var so that the scope is constrained.

5 Likes

Thanks that is the kind of practical info I like.

The target is a "weak" IOT class machine which is kept pretty busy pushing data to the AI co-processor and decoding multiple rtsp streams. That said I've been impressed with node-red performance from the beginning. MQTT is the unsung hero gluing it all together.

If I plan to load them from a file on Deploy they'd have to be variables, correct?

I don't think I fully understand what the let does in the context of a node-red function, as I thought the scope was automatically limited to the function node unless I made them flow or other context variables. I do use flow context variables to maintain the current state (or mode -- idle, audio, or notify) across function calls.

@Cristian-Me the bulk of the data is in the message object and is totally different for every call. The if-else sieve is to handle camera specific "special cases" and change downstream behavior via msg.InRegion true/false based on msg.endX and msg.endY data values

Putting the camera specific polygon values in to context variables after reading them from a file on Deploy is where I think it should end up, but that entails using a text editor to create a json object or other file format. initially I think its easier to use the built-in node-red function editor to change the values. I've already changed them to @Colin const suggestion, as that makes sense for now.

I think someone else suggested this but just in case you missed it...

You can use an inject node set to operate on startup that feeds to a Change node that moves JSON into a flow or global context variable. Then in your function you simply retrieve the object, fully formed, ready for use.

This has a several advantages including....

  • you could have a dashboard editor to update them.
  • the change node has a graphical JSON editor.
  • objects are initialised only once.

To name a few.

2 Likes

That's what I do for my MQTT device lookup table. The starup inject triggers a file read which is used to create a global context table. I also have a dashboard button which sends the same message to reload if I've edited the table, so I don't have to restart the flows.

It's pretty easy to implement, and there are lots of nodes for converting between e.g. .csv and JSON objects.

The processor overheads of a function like this will be absolutely trivial compared to decoding rtsp. Run top (or whatever is equivalent in the device) to watch the usage of node-red and drive your function with an inject node (with the output of the function node disconnected). Initially set the inject node to fire once a second, then 10 times/second then 100 times/second and see what the effect on the usage of node-red is. How often will you be calling it?

In a situation like this where you have just a single function inside the function node and you are declaring the variables at the start there is no difference between var and let. However as you write more complex code then it is good practice to declare variables in the block or function where they are used and then you have to use let. So I think it is best to get into the habit of using let.

1 Like

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.