Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GeometryGraph and PreparedGeometry multi-threaded (Send) #1198

Open
wants to merge 17 commits into
base: main
Choose a base branch
from

Conversation

gauteh
Copy link

@gauteh gauteh commented Jul 8, 2024

Make GeometryGraph and PreparedGeometry Send so that it can be used by multiple threads.

Based on and depends on: #1197

@gauteh gauteh changed the title graph send GeometryGraph and PreparedGeometry multi-threaded (Send) Jul 8, 2024
@michaelkirk
Copy link
Member

#1197 has been merged - could you do a rebase or merge to minimize the diff?

@gauteh
Copy link
Author

gauteh commented Jul 27, 2024

I have rebased, but I haven't had time to work any further on this yet (feel free to make changes if you have time).

@gauteh
Copy link
Author

gauteh commented Aug 16, 2024

Some of the test fails seem like numeric precision errors, they are also present on main. There is one failing test left (debug assert), which I don't know why is failing.

 3  failed: algorithm::interior_point::test::polygon_cell_test                          ▐
 thread 'algorithm::interior_point::test::polygon_cell_test' panicked at geo/src/algorit▐
 hm/relate/geomgraph/edge_end_bundle_star.rs:112:21:                                    ▐
 side_location conflict with coordinate: Coord { x: 2.0, y: 1.0 }, right_location: Insid▐
 e, current_location: Outside                                                           ▐

@gauteh
Copy link
Author

gauteh commented Aug 16, 2024

The lifetime in prepared geometry is tedious to work with. Could that be replaced by a Cow or maybe a owned version of the geometrygraph?

@gauteh
Copy link
Author

gauteh commented Aug 16, 2024

The lifetime in prepared geometry is tedious to work with. Could that be replaced by a Cow or maybe a owned version of the geometrygraph?

Just realized that is exactly how it is now...

@gauteh
Copy link
Author

gauteh commented Aug 17, 2024

This seems to be working now.

It appears that a new geometrygraph + intersection matrix is created for every point I want to check against the preparedgeometry.

@urschrei
Copy link
Member

It appears that a new geometrygraph + intersection matrix is created for every point I want to check against the preparedgeometry.

Can you expand on this and / or illustrate it with a minimal test case?

@gauteh
Copy link
Author

gauteh commented Aug 17, 2024

My use case is roaring-landmask, here's a PR where I am running against these changes: gauteh/roaring-landmask#28, in https://github.com/gauteh/roaring-landmask/blob/georust-prepared/src/shapes.rs#L129 I check if a point is in any of the polygons using is_covers:

self.geom.relate(&p).is_covers()

this creates a geometrygraph of the point p and tests it against the PreparedGeometry. I believe geos has a PreparedPolygon/PreparedMultiPolygon with specializations for checking various relates towards a point.

In roaring-landmask this may be tested with e.g.: cargo test --features nightly --release -j 1 many -- --nocapture

@michaelkirk
Copy link
Member

michaelkirk commented Aug 17, 2024

It appears that a new geometrygraph + intersection matrix is created for every point I want to check against the preparedgeometry.

That's right. The difference with PreparedGeometry is that the Polygon's geometry graph isn't created from scratch every time.

The point's graph will be created from scratch, since it's a different point every time, but it should also be a pretty trivial graph.

I check if a point is in any of the polygons using is_covers:

Note that in the case of points and polygons, polygon.relate(points).is_covers() is the same as polygon.instersects(point).

The GeometryGraph is a very general tool, built to handle all the de-9im relations for all geometry types. If you are only interested in "point intersects polygon" checks, there is likely a faster approach involving less machinery - maybe ray casting, but I don't think we have an implementation of that yet.

@gauteh
Copy link
Author

gauteh commented Aug 18, 2024

It appears that a new geometrygraph + intersection matrix is created for every point I want to check against the preparedgeometry.

That's right. The difference with PreparedGeometry is that the Polygon's geometry graph isn't created from scratch every time.

The point's graph will be created from scratch, since it's a different point every time, but it should also be a pretty trivial graph.

Indeed, the tree has no members.

I check if a point is in any of the polygons using is_covers:

Note that in the case of points and polygons, polygon.relate(points).is_covers() is the same as polygon.instersects(point).

Is there any performance difference? Or exactly the same?

The GeometryGraph is a very general tool, built to handle all the de-9im relations for all geometry types. If you are only interested in "point intersects polygon" checks, there is likely a faster approach involving less machinery - maybe ray casting, but I don't think we have an implementation of that yet.

So it appears, and that makes sense (to start with). I think that this PR is mostly functional now. I had to make a few slightly messy changes to satisfy borrowing rules, without doing a much greater re-structuring. There is some cleanup to be done, but it should be a useful feature (and actually a pretty cool plus over geos, where it is not safe to share the PreparedGeometry) to other applications. Sadly, I don't think it will make much difference to my issue (#1184).

let segment_intersector = self
.graph_a
.compute_edge_intersections(&self.graph_b, Box::new(self.line_intersector.clone()));
// let segment_intersector = self
Copy link
Member

Choose a reason for hiding this comment

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

I'm not clear on what's happening here.

@urschrei
Copy link
Member

@michaelkirk I've rebased and tidied this up and appeased young master Clipperton – I only have one query regarding the "satisfy rust's borrowing rules section" but otherwise this hasn't added any obvious complexity to our GeomGraph machinery (though it's difficult to imagine how that would be possible…), and the Send bound will probably come in useful at some point. WDYT?

@@ -35,7 +34,7 @@ where
{
arg_index: usize,
parent_geometry: GeometryCow<'a, F>,
tree: Option<Rc<RTree<Segment<F>>>>,
tree: Option<RTree<Segment<F>>>,
Copy link
Member

Choose a reason for hiding this comment

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

There are some positive changes in this PR, but there is a serious perf regression with:

 relate prepared polygons
                        time:   [142.78 ms 143.14 ms 143.63 ms]
                        change: [+192.56% +193.59% +194.76%] (p = 0.00 < 0.05)
                        Performance has regressed.

I haven't zeroed in on the cause yet - but this line is suspect. The point of the Rc<Tree> is that it was cheap to clone. It seems like we've removed that and now have a "cache the tree and invalidate it as necessary" approach.

Note there are also some nice performance wins to the non-prepared geometry operations in this PR, but I personally don't think it's worth it as phrased.

Copy link
Member

Choose a reason for hiding this comment

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

Ooof yeah that regression is a show-stopper at the moment.

Copy link
Member

@michaelkirk michaelkirk left a comment

Choose a reason for hiding this comment

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

whoops - didn't mean to approve yet.

Copy link
Member

@michaelkirk michaelkirk left a comment

Choose a reason for hiding this comment

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

It's nice to get rid of the Rc<RefCell<T>>, but there is a lot of new complexity for the sake of Send. In the meanwhile, people can Send their geometry and make a PreparedGeometry on each thread.

Is there a way it can be simplified?

}
}

fn invalidate_tree(&mut self) {
Copy link
Member

Choose a reason for hiding this comment

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

This got a little more complex.

edge_1,
segment_1.segment_idx,
);
if segment_1.edge_idx == segment_0.edge_idx {
Copy link
Member

Choose a reason for hiding this comment

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

This got quite a lot more complex.

@@ -95,25 +94,85 @@ where
false
}

// Copy of `add_intersections` specialized for a single 'edge' against itself.
pub fn add_intersections_against_self(
Copy link
Member

Choose a reason for hiding this comment

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

This got quite a lot more complex.

// this is a copy of the above functionality to satisfy rust borrowing rules.
// from here [...
let mut segment_intersector =
SegmentIntersector::new(Box::new(self.line_intersector.clone()), false);
Copy link
Member

Choose a reason for hiding this comment

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

This got a bit more complex.

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.

3 participants