Skip to content

Performance Optimizations + Minor Bugfix#93

Open
Jakob-Gliwa wants to merge 6 commits into
codmpm:masterfrom
Jakob-Gliwa:master
Open

Performance Optimizations + Minor Bugfix#93
Jakob-Gliwa wants to merge 6 commits into
codmpm:masterfrom
Jakob-Gliwa:master

Conversation

@Jakob-Gliwa

Copy link
Copy Markdown
Contributor

Hi there - thank you for your node-red Loxone Plugin.
Been using it for years!

I was recently wondering why my node-red docker was so busy (10-15% CPU on an Intel N100) and traced it back to this plugin.

Major Improvement:
Core issue is/was that the current version iterates over all controls on every event.
Due to many energy-meters I have tons of events, so this operation was pretty costly.
I refactored it to a map.
For me this reduced the CPU pressure from 10-15% to about 0,5-2%.
=> If you just accept minor changes, this would be the most important one

Minor Improvements
Whilst on it I also added a couple of other performance improvements (caching the structured states, holding nodes directly accessible instead of iterations through all nodes, bit optimized Value Event parsing, etc.)

Bugfix
Also I fixed a bug in the deregistering of nodes (removals within loop).

Disclaimer:
I'm not super proficient in JS so I used AI (mainly Opus 4.5, GPT 5.2 Pro) to a) find the issues, b) Benchmark & Test the Optimizations and c) fix the issues.

acidcliff added 6 commits January 1, 2026 19:28
- Add _stateIndex Map in prepareStructure() for O(1) lookups
- Replace O(n) linear search in findControlByState() with Map lookup
- Support both string UUIDs and array UUIDs (multi-value states)
- Two-pass approach: build controls first, then index (parent priority)
- Reduces lookup time from 31.6µs to <0.01µs per event
- Add _keepaliveNodes array in constructor for direct node management
- Replace RED.nodes.eachNode() in keepalive handler with direct array iteration
- Replace RED.nodes.eachNode() in sendOnlineNodeMsg() with direct array iteration
- Add registerKeepaliveNode() and deregisterKeepaliveNode() methods
- Update LoxoneKeepaliveNode to register/deregister on create/close
- Reduces iteration from O(n) all nodes to O(k) relevant nodes only
- Replace buggy forEach+splice pattern with indexOf+splice in all deregister methods
- Fix deregisterOnlineNode(), deregisterInputNode(), deregisterOutputNode()
- Fix deregisterStreamInNode(), deregisterStreamAllNode(), deregisterWebserviceNode()
- Fix removeWebserviceNodeFromQueue() using findIndex for handler+uri matching
- Prevents skipping elements when array is modified during iteration
- Improves performance by eliminating callback overhead and using native indexOf
- deregisterKeepaliveNode() already fixed in Opt codmpm#3
- Add _parseEventValue() helper function with type-check first
- Avoid expensive try/catch exceptions on Numbers/Objects (node-lox-ws-api returns native types)
- Use Set for fast JSON start character lookup
- Replace try/catch JSON.parse in buildMsgObject() with optimized parser
- Prevents exception overhead: Numbers/Objects returned directly without parsing
- Only parse strings that start with { or [ (potential JSON)
- Mixed workload: 81x faster (80% Numbers, 15% Strings, 5% Objects)
- Numbers: 10x faster, Strings: 144x faster, Objects: 886-913x faster
…rs to module scope

Optimization codmpm#9: Denormalized Index (+6% speedup)
- Pre-resolve room/category names in state index during structure load
- Avoids 2x Object lookup per event at runtime
- Extend state index entries with roomName and categoryName fields

Optimization codmpm#10: Message Template Cache (+7% speedup)
- Pre-build message templates with all static fields
- Only payload changes per event (template clone + payload update)
- Reduces object creation overhead significantly

Code improvements:
- Compact buildMsgObject() implementation (22 lines saved, 54% reduction)
- Use spread operator for template cloning
- Use optional chaining and nullish coalescing for cleaner code
- Move helper functions to module scope (not recreated per instance)
  - limitString: arrow function with template literals
  - parseEventValue: module-level function
  - JSON_START: module-level constant
- Fix _parseEventValue scope issue (was causing ReferenceError)

Total expected speedup: +13% (from Opt codmpm#9 + codmpm#10)
- Change node-lox-ws-api dependency from codm/node-lox-ws-api to Jakob-Gliwa/node-lox-ws-api
- Bump version to 0.10.13.1 for testing new lox-ws-api fork
@codmpm

codmpm commented Jan 6, 2026

Copy link
Copy Markdown
Owner

Hey Jakob.
Much appreciated ❤️

Could you also do a pull request for https://github.com/codm/node-lox-ws-api/ with your changes made to the underlying API.
Happy to test and merge then.

Cheers,
Patrik

@Jakob-Gliwa

Copy link
Copy Markdown
Contributor Author

Hey Patrik,

all changes except 1e81ab3 will work without changes to the lox-ws-api.

Is it possible to just accept the other commits and ignore the changes to package.json?

I just added it to test out some perf-relevant changes to the lox-ws-api - mainly I ported it to the native ws lib from websocket, which worked quite well. I you are interested in that change I could also open a pull request for that.

Best,
Jakob

@codmpm

codmpm commented Jan 6, 2026

Copy link
Copy Markdown
Owner

Would be great to also get the changes for lox-ws-api 👍🏻

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants