perf(boolean-contains): avoid repeated line scans in LineString check#3102
perf(boolean-contains): avoid repeated line scans in LineString check#3102rexxars wants to merge 1 commit into
Conversation
isLineOnLine called booleanPointOnLine up to twice per vertex - once for membership, once for the interior probe - and every call re-validated its inputs, allocated an options object, and scanned lineString1 from the first segment. Replace the calls with a local helper that walks lineString1's raw coordinates once and reports whether a point is off the line, only on its start or end vertex, or on its interior. Same segment math as booleanPointOnLine, including the zero-length segment case. Line/line benchmarks run 3.1-3.5x faster, e.g. LinesExactlySame goes from 6.5M to 21.5M ops/sec on a MacBook Pro M4.
|
@mfedderly I don't love replicating logic from other modules like this, but I don't know if there's a convention for skipping the normalization piece (types signatures would need overloads etc)? How do you feel about multiple exports per package? Eg could we export a named |
|
Re multiple exports per package: We have this in some places and it can work, but I want to hold the line of "it doesn't get exported unless its a public API surface we intend to support", so we should try to avoid sharing some internal method just for code reuse purposes. I just made this PR as a potential increased API in We've thrown about other ideas for internal code sharing, but most of them wind up with needing to publish a package with an internal API and then hope nobody uses it directly. We could adopt some kind of bundler step, or perhaps some directory of files that are copied to target projects so we introduce duplication in the dists only. I'm not sure if you've got any other ideas on how to code share without making it a public API surface? I was hoping to get away with the entire build step for Turf@v8 just being I think that some code duplication can work, but the tradeoff comes from how we expect the maintenance burden to work out. We're unlikely to have a problem with two independent implementations of cross-product, but as things grow from there the maintenance gets more painful. |
mfedderly
left a comment
There was a problem hiding this comment.
I didn't scrutinize the implementation here too closely, but from a high level this seems like duplication that I'd approve and merge if someone said they had a compelling need for the increased performance. As it stands the method is already relatively fast (at least for our test fixtures) and so without someone calling for increased performance we could also get away with just leaving things as is.
At the end of the day someone can always copy/paste the Turf implementation into their own codebase and hyper-optimize it for their exact usage and outperform a more generic implementation. Turf did exactly this for simplify's copy of simplify-js (we use number[] instead of the original {x: number, y: number}.
So I guess I'm curious for feedback from you: is this increased performance useful for you? If so I can take another look here and we can go down the rabbit hole of figuring out a good way to duplicate code across the modules without incurring actual duplication in the code.
|
It's not important to me, I was just trying to narrow down/fix any perf regressions in #3011 since you called it out |
|
I don't love the regressions in performance, but I'm willing to tolerate them for additional correctness. Everything here is still pretty fast on an absolute scale. Unless its impacting someone I value code clarity and deduplication over the absolute best possible performance. If its OK with you I'd move to close this PR for now, and if someone feels strongly that the additional performance would benefit them we can revive it and get it landed then. |
Yes, this is a tough one. Not recommending this, but you could put shared sources in a non-published directory, include them from the consuming package's tsconfig, and set rootDir to the parent. tsc then emits the shared file into each consumer's own dist. The relative import survives and resolves inside The other approach would be to just clearly mark private helpers as
Sure. I can maybe open a discussion instead where we can talk about perf-related details: I was surprised to find |
|
👍 sounds good re: discussion. I actually noticed a few days ago that my suggested behavior of "treat geojson as immutable (like React params) and only clone things you actually change" is actually really hard with things like |
isLineOnLinecalledbooleanPointOnLineup to twice per vertex - once for membership and once for the interior probe, and every call re-validated its inputs and allocated an options object, then scannedlineString1from the first segment.This PR replaces the calls with a local helper that walks
lineString1's raw coordinates once and reports whether a point is off the line or not, only on its start or end vertex, or on its interior. Same segment math asbooleanPointOnLine, including the zero-length segment case.Line/line benchmarks run about 3.1-3.5x faster, e.g.
LinesExactlySamegoes from 6.5M to 21.5M ops/sec on a MacBook Pro M4.