dequelabs / axe-core

Accessibility engine for automated Web UI testing
https://www.deque.com/axe/
Mozilla Public License 2.0
5.75k stars 746 forks source link

perf: memoize DqElement #4452

Closed straker closed 1 month ago

straker commented 1 month ago

Noticed this when trying to debug perf issues in duplicate-id-aria. We've run into problems on sites that have a module repeat 1000s of times on the page and the module has an aria id that is also then repeated. Axe-core would take a really long time to run the rule. Looking into it what I discovered is that a majority of the time was spent on resolving the relatedNodes for each check. Since each each duplicate id node was also in the relatedNodes for every other node, this caused the single node to be converted to a DqElement n times. This lead to many performance problems, but specifically calling the getSelector of a DqElement would call outerHTML for the node n*2 times which would be very slow.

To solve this I decided to memoize the DqElement creation. That way a single node will only ever output a single DqElement, thus saving significant time in creation. Not only that but every time a node appears in a result (either as the check node or a related node) the memory is now shared so this change also reduces the in-memory size of the results object.

Testing a simple page with 5000 nodes of duplicate id, here are the results for running the duplicate-id-aria check.

Before change (in ms) After change (in ms)
21,280.1 11,841.1

Flamechart before the change. The majority of the time is spent in getSelector

Chrome performance timer of getSelector showing it spent 12000ms total in the function

Flamechart after the change. Time is now spent mostly resolving the cache which results in no time spent in getSelector