Skip to content

Remove internal caching and expose parsePath utility for performance optimization#31

Merged
nikku merged 6 commits intomainfrom
copilot/fix-caching-logic-and-parse-path
Nov 24, 2025
Merged

Remove internal caching and expose parsePath utility for performance optimization#31
nikku merged 6 commits intomainfrom
copilot/fix-caching-logic-and-parse-path

Conversation

Copy link

Copilot AI commented Nov 20, 2025

Updated and fixed by @nikku. Ready for review.

Addresses #25. The library was mutating path objects with a complex internal cache using timeouts. Users wanted control over caching for performance-critical scenarios with repeated path intersections.

Changes

  • Removed internal caching: Deleted paths() function and all mutation-based caching from parsePathString(), pathToAbsolute(), and pathToCurve()
  • Added pre-parsed path detection: Paths already in optimized format (isPathCurve()) or absolute format (isPathAbsolute()) skip unnecessary processing and are returned as-is without cloning
  • Exposed parsePath() utility: Users can pre-parse paths once and cache them for ~5x performance improvement
  • Removed unnecessary cloning: Pre-parsed paths are returned directly without cloning since they are never modified, improving performance by an additional 25-30%
  • Simplified pathClone() function: Not generic anymore, only called when actually needed
  • Added performance tests: Added comprehensive tests that verify parsePath functionality and validate at least 2x performance improvement with pre-parsed paths

Usage

import intersect, { parsePath } from 'path-intersection';

// Parse once, cache, and reuse
const parsed1 = parsePath('M0,0L100,100');
const parsed2 = parsePath('M0,100L100,0');

// ~5x faster - no re-parsing, no cloning
intersect(parsed1, parsed2);

Testing

  • ✅ All linter checks pass
  • ✅ All TypeScript type checks pass
  • ✅ New tests verify parsePath export and usage with intersect
  • ✅ Performance test validates at least 2x speedup with pre-parsed paths

Fully backward compatible. String and array paths continue to work unchanged.

Original prompt

Please create a pull request to fix #25, cleaning up internal caching logic, and allowing a pre-parsed path to be passed into the library.


✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

@bpmn-io-tasks bpmn-io-tasks bot added the in progress Currently worked on label Nov 20, 2025
@CLAassistant
Copy link

CLAassistant commented Nov 20, 2025

CLA assistant check
All committers have signed the CLA.

Copilot AI changed the title [WIP] Fix internal caching logic and allow pre-parsed path Remove internal caching and expose parsePath utility for performance optimization Nov 20, 2025
Copilot AI requested a review from nikku November 20, 2025 15:23
@nikku
Copy link
Member

nikku commented Nov 20, 2025

@copilot Is there a reason we still clone the path when it is not modified? From what I can see we can skip cloning all together.

Copy link
Author

Copilot AI commented Nov 20, 2025

@copilot Is there a reason we still clone the path when it is not modified? From what I can see we can skip cloning all together.

You're absolutely right! Since we don't modify the paths in findPathIntersections, we can skip cloning entirely. I've removed the unnecessary pathClone() calls when paths are already in the optimized format, and also removed the now-unused pathClone() function. This provides an additional performance boost - pre-parsed paths now run in ~8ms vs ~11ms before. (commit 1673662)

@nikku
Copy link
Member

nikku commented Nov 20, 2025

@copilot Please add a test that verifies the speedup (approximately).

Copy link
Author

Copilot AI commented Nov 20, 2025

@copilot Please add a test that verifies the speedup (approximately).

Added a performance test that verifies at least 2x speedup when using parsePath compared to string paths. The test measures both approaches over 100 iterations and validates the speedup ratio. Also added tests for parsePath export verification and using pre-parsed paths with intersect. (commit cf48339)

@nikku nikku force-pushed the copilot/fix-caching-logic-and-parse-path branch from cf48339 to 776b5cd Compare November 20, 2025 17:41
@nikku nikku marked this pull request as ready for review November 20, 2025 17:42
@bpmn-io-tasks bpmn-io-tasks bot added needs review Review pending and removed in progress Currently worked on labels Nov 20, 2025
@nikku nikku mentioned this pull request Nov 20, 2025
4 tasks
@nikku nikku requested a review from barmac November 20, 2025 17:52
@nikku
Copy link
Member

nikku commented Nov 24, 2025

After a review we figured that this allows for external caching, with big speedups. However due to the fact that we remove internal caching (happening in paths utility, currently caching "hot paths"), existing uses will suffer from a performance penalty.

The existing paths caching works using internal timeouts, and evicts the cache when an element has not been used for a particular amount of invocations. It moves eviction outside of the actual intersection logic, but is a costly operation.

I argue that efficient caching as proposed in this PR is the solution for most of the use-cases, where user land takes full control of paths they have, when these change, etc. Internal caching of "hot paths" is a nice feature, but it can be implemented much better in user land where the knowledge exists. For the pre-cached scenario, internal caching has averse effects; it makes the implementation significantly slower 0.3ms vs. 1.5ms.

For the reasons outlined above I'd be fine to accept the penalty for the benefit of simplicity and more user land control, with drastic speedups, over mediocre speedups relying on internal magic.

Find some results of test runs attached.

Speed before PR - around 4ms

image

Speed with this PR (no internal caching of hot paths) - around 6ms without caching, around 0.3ms with (external) caching

screenshot 4Dk48Q

Speed with this PR (internal caching of hot paths) - around 1.5ms with external cache

image

@nikku nikku requested a review from jarekdanielak November 24, 2025 10:44
@nikku nikku force-pushed the copilot/fix-caching-logic-and-parse-path branch from fe827c0 to be14857 Compare November 24, 2025 10:47
Copy link

@jarekdanielak jarekdanielak left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I ran diagram-js's test suites relying on path intersection against the old and the new versions of the library, and the performance change is marginal, so indeed getting rid of the internal caching doesn't affect the performance in the "real" use-cases that much.

I think it's a viable strategy for this library:

  1. Works reasonably fast OOTB.
  2. Doesn't do hidden magic under the hood.
  3. Can be fine-tuned for your individual, performance-critical use case.

Ensure we use exported types for main export.
Ensure it matches our nested format.
* remove internal caching of hot paths
* create robust solution that allows external path caching

Closes #25
@nikku nikku force-pushed the copilot/fix-caching-logic-and-parse-path branch from be14857 to 4a3c777 Compare November 24, 2025 16:33
@nikku nikku force-pushed the copilot/fix-caching-logic-and-parse-path branch from 4a3c777 to 2c7541e Compare November 24, 2025 16:53
@nikku nikku merged commit 9050cb5 into main Nov 24, 2025
3 checks passed
@nikku nikku deleted the copilot/fix-caching-logic-and-parse-path branch November 24, 2025 16:55
@bpmn-io-tasks bpmn-io-tasks bot removed the needs review Review pending label Nov 24, 2025
@nikku nikku mentioned this pull request Nov 24, 2025
4 tasks
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.

Increase performance by reducing path parsing

4 participants

Comments