From 32e01437b2dec3ef3f7da39b1454b1ac21aa1bd7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stephan=20H=C3=BCgel?= Date: Wed, 6 Nov 2024 10:54:20 +0000 Subject: [PATCH 1/8] Add multithreading support to Multi* geometries --- geo-types/CHANGES.md | 2 ++ geo-types/Cargo.toml | 2 ++ geo-types/src/geometry/multi_line_string.rs | 32 ++++++++++++++++++++ geo-types/src/geometry/multi_point.rs | 32 ++++++++++++++++++++ geo-types/src/geometry/multi_polygon.rs | 33 +++++++++++++++++++++ geo/Cargo.toml | 2 +- 6 files changed, 102 insertions(+), 1 deletion(-) diff --git a/geo-types/CHANGES.md b/geo-types/CHANGES.md index 79bb76114d..4b4b0cdb2f 100644 --- a/geo-types/CHANGES.md +++ b/geo-types/CHANGES.md @@ -2,6 +2,8 @@ ## Unreleased * Add rstar compatibility for MultiPolygon +* Add multi-threading support to Multi* geometries. + * Feature-gated ('multithreading'), disabled by default, enabled by default when geo-types is used by geo ## 0.7.13 diff --git a/geo-types/Cargo.toml b/geo-types/Cargo.toml index 17c95d39d7..6fd4eea997 100644 --- a/geo-types/Cargo.toml +++ b/geo-types/Cargo.toml @@ -13,6 +13,7 @@ edition = "2021" [features] default = ["std"] std = ["approx?/std", "num-traits/std", "serde?/std"] +multithreading = ["rayon"] # Prefer `use-rstar` feature rather than enabling rstar directly. # rstar integration relies on the optional approx crate, but implicit features cannot yet enable other features. # See: https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#namespaced-features @@ -25,6 +26,7 @@ use-rstar_0_11 = ["rstar_0_11", "approx"] use-rstar_0_12 = ["rstar_0_12", "approx"] [dependencies] +rayon = { version = "1.10.0", optional = true } approx = { version = ">= 0.4.0, < 0.6.0", optional = true, default-features = false } arbitrary = { version = "1.2.0", optional = true } num-traits = { version = "0.2", default-features = false, features = ["libm"] } diff --git a/geo-types/src/geometry/multi_line_string.rs b/geo-types/src/geometry/multi_line_string.rs index 11b0c8ff88..4fcdfa4666 100644 --- a/geo-types/src/geometry/multi_line_string.rs +++ b/geo-types/src/geometry/multi_line_string.rs @@ -5,6 +5,8 @@ use alloc::vec::Vec; #[cfg(any(feature = "approx", test))] use approx::{AbsDiffEq, RelativeEq}; use core::iter::FromIterator; +#[cfg(feature = "multithreading")] +use rayon::prelude::*; /// A collection of /// [`LineString`s](line_string/struct.LineString.html). Can @@ -118,6 +120,36 @@ impl MultiLineString { } } +#[cfg(feature = "multithreading")] +impl IntoParallelIterator for MultiLineString { + type Item = LineString; + type Iter = rayon::vec::IntoIter>; + + fn into_par_iter(self) -> Self::Iter { + self.0.into_par_iter() + } +} + +#[cfg(feature = "multithreading")] +impl<'a, T: CoordNum + Sync> IntoParallelIterator for &'a MultiLineString { + type Item = &'a LineString; + type Iter = rayon::slice::Iter<'a, LineString>; + + fn into_par_iter(self) -> Self::Iter { + self.0.par_iter() + } +} + +#[cfg(feature = "multithreading")] +impl<'a, T: CoordNum + Send + Sync> IntoParallelIterator for &'a mut MultiLineString { + type Item = &'a mut LineString; + type Iter = rayon::slice::IterMut<'a, LineString>; + + fn into_par_iter(self) -> Self::Iter { + self.0.par_iter_mut() + } +} + #[cfg(any(feature = "approx", test))] impl RelativeEq for MultiLineString where diff --git a/geo-types/src/geometry/multi_point.rs b/geo-types/src/geometry/multi_point.rs index ff3c16c6f8..c596333daf 100644 --- a/geo-types/src/geometry/multi_point.rs +++ b/geo-types/src/geometry/multi_point.rs @@ -6,6 +6,8 @@ use approx::{AbsDiffEq, RelativeEq}; use alloc::vec; use alloc::vec::Vec; use core::iter::FromIterator; +#[cfg(feature = "multithreading")] +use rayon::prelude::*; /// A collection of [`Point`s](struct.Point.html). Can /// be created from a `Vec` of `Point`s, or from an @@ -85,6 +87,36 @@ impl<'a, T: CoordNum> IntoIterator for &'a mut MultiPoint { } } +#[cfg(feature = "multithreading")] +impl IntoParallelIterator for MultiPoint { + type Item = Point; + type Iter = rayon::vec::IntoIter>; + + fn into_par_iter(self) -> Self::Iter { + self.0.into_par_iter() + } +} + +#[cfg(feature = "multithreading")] +impl<'a, T: CoordNum + Sync> IntoParallelIterator for &'a MultiPoint { + type Item = &'a Point; + type Iter = rayon::slice::Iter<'a, Point>; + + fn into_par_iter(self) -> Self::Iter { + self.0.par_iter() + } +} + +#[cfg(feature = "multithreading")] +impl<'a, T: CoordNum + Send + Sync> IntoParallelIterator for &'a mut MultiPoint { + type Item = &'a mut Point; + type Iter = rayon::slice::IterMut<'a, Point>; + + fn into_par_iter(self) -> Self::Iter { + self.0.par_iter_mut() + } +} + impl MultiPoint { pub fn new(value: Vec>) -> Self { Self(value) diff --git a/geo-types/src/geometry/multi_polygon.rs b/geo-types/src/geometry/multi_polygon.rs index fefeaf8579..a40ea99650 100644 --- a/geo-types/src/geometry/multi_polygon.rs +++ b/geo-types/src/geometry/multi_polygon.rs @@ -4,7 +4,10 @@ use alloc::vec; use alloc::vec::Vec; #[cfg(any(feature = "approx", test))] use approx::{AbsDiffEq, RelativeEq}; + use core::iter::FromIterator; +#[cfg(feature = "multithreading")] +use rayon::prelude::*; /// A collection of [`Polygon`s](struct.Polygon.html). Can /// be created from a `Vec` of `Polygon`s, or from an @@ -75,6 +78,36 @@ impl<'a, T: CoordNum> IntoIterator for &'a mut MultiPolygon { } } +#[cfg(feature = "multithreading")] +impl IntoParallelIterator for MultiPolygon { + type Item = Polygon; + type Iter = rayon::vec::IntoIter>; // Parallel iterator type for Vec> + + fn into_par_iter(self) -> Self::Iter { + self.0.into_par_iter() + } +} + +#[cfg(feature = "multithreading")] +impl<'a, T: CoordNum + Sync> IntoParallelIterator for &'a MultiPolygon { + type Item = &'a Polygon; + type Iter = rayon::slice::Iter<'a, Polygon>; // Parallel iterator type for a slice of Polygon + + fn into_par_iter(self) -> Self::Iter { + self.0.par_iter() + } +} + +#[cfg(feature = "multithreading")] +impl<'a, T: CoordNum + Send + Sync> IntoParallelIterator for &'a mut MultiPolygon { + type Item = &'a mut Polygon; + type Iter = rayon::slice::IterMut<'a, Polygon>; // Parallel iterator type for a mutable slice of Polygon + + fn into_par_iter(self) -> Self::Iter { + self.0.par_iter_mut() + } +} + impl MultiPolygon { /// Instantiate Self from the raw content value pub fn new(value: Vec>) -> Self { diff --git a/geo/Cargo.toml b/geo/Cargo.toml index d48be60f2e..330d1c3990 100644 --- a/geo/Cargo.toml +++ b/geo/Cargo.toml @@ -17,7 +17,7 @@ default = ["earcutr", "spade", "multithreading"] use-proj = ["proj"] proj-network = ["use-proj", "proj/network"] use-serde = ["serde", "geo-types/serde"] -multithreading = ["i_overlay/allow_multithreading"] +multithreading = ["i_overlay/allow_multithreading", "geo-types/multithreading"] [dependencies] earcutr = { version = "0.4.2", optional = true } From 6df4c2f6dff54743b349c6e23e6e9e287ca53366 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stephan=20H=C3=BCgel?= Date: Wed, 6 Nov 2024 19:10:51 +0000 Subject: [PATCH 2/8] Update multithreading flag docs --- geo/src/lib.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/geo/src/lib.rs b/geo/src/lib.rs index 88bf61aa85..0d1cde38f6 100644 --- a/geo/src/lib.rs +++ b/geo/src/lib.rs @@ -191,7 +191,8 @@ //! - Allows geometry types to be serialized and deserialized with [Serde]. //! - ☐ Disabled by default. //! - `multithreading`: -//! - Enables multithreading support for the `i_overlay` crate. +//! - Enables multithreading support for the `i_overlay` crate (via Rayon), and activates the `multithreading` flag +//! in `geo-types`, enabling multi-threaded iteration over `Multi*` geometries. //! - ☑ Enabled by default. //! //! # Ecosystem From 1137aef19aca9c2c2b950e0715e2cf4c401dcc11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stephan=20H=C3=BCgel?= Date: Wed, 6 Nov 2024 19:14:51 +0000 Subject: [PATCH 3/8] Add multithreading feature to geo-types top-level docs --- geo-types/src/lib.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/geo-types/src/lib.rs b/geo-types/src/lib.rs index 00e4a6eccb..df385554e4 100644 --- a/geo-types/src/lib.rs +++ b/geo-types/src/lib.rs @@ -62,6 +62,8 @@ //! The following optional [Cargo features] are available: //! //! - `std`: Enables use of the full `std` library. Enabled by default. +//! - `multithread`: Enables multi-threaded iteration over `Multi*` geometries. **Disabled** +//! by default but **enabled** by `geo`'s default features. //! - `approx`: Allows geometry types to be checked for approximate equality with [approx] //! - `arbitrary`: Allows geometry types to be created from unstructured input with [arbitrary] //! - `serde`: Allows geometry types to be serialized and deserialized with [Serde] From df64658693e63b33423b061ba2648759fcf9589c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stephan=20H=C3=BCgel?= Date: Wed, 6 Nov 2024 19:38:13 +0000 Subject: [PATCH 4/8] Fix feature name --- geo-types/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/geo-types/src/lib.rs b/geo-types/src/lib.rs index df385554e4..a4d12ccc30 100644 --- a/geo-types/src/lib.rs +++ b/geo-types/src/lib.rs @@ -62,7 +62,7 @@ //! The following optional [Cargo features] are available: //! //! - `std`: Enables use of the full `std` library. Enabled by default. -//! - `multithread`: Enables multi-threaded iteration over `Multi*` geometries. **Disabled** +//! - `multithreading`: Enables multi-threaded iteration over `Multi*` geometries. **Disabled** //! by default but **enabled** by `geo`'s default features. //! - `approx`: Allows geometry types to be checked for approximate equality with [approx] //! - `arbitrary`: Allows geometry types to be created from unstructured input with [arbitrary] From 76200e7d9d3a0ab96a00d4aeb96437468f3815aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stephan=20H=C3=BCgel?= Date: Wed, 6 Nov 2024 19:39:45 +0000 Subject: [PATCH 5/8] Remove needless comments --- geo-types/src/geometry/multi_polygon.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/geo-types/src/geometry/multi_polygon.rs b/geo-types/src/geometry/multi_polygon.rs index a40ea99650..8ac2bf8c95 100644 --- a/geo-types/src/geometry/multi_polygon.rs +++ b/geo-types/src/geometry/multi_polygon.rs @@ -81,7 +81,7 @@ impl<'a, T: CoordNum> IntoIterator for &'a mut MultiPolygon { #[cfg(feature = "multithreading")] impl IntoParallelIterator for MultiPolygon { type Item = Polygon; - type Iter = rayon::vec::IntoIter>; // Parallel iterator type for Vec> + type Iter = rayon::vec::IntoIter>; fn into_par_iter(self) -> Self::Iter { self.0.into_par_iter() @@ -91,7 +91,7 @@ impl IntoParallelIterator for MultiPolygon { #[cfg(feature = "multithreading")] impl<'a, T: CoordNum + Sync> IntoParallelIterator for &'a MultiPolygon { type Item = &'a Polygon; - type Iter = rayon::slice::Iter<'a, Polygon>; // Parallel iterator type for a slice of Polygon + type Iter = rayon::slice::Iter<'a, Polygon>; fn into_par_iter(self) -> Self::Iter { self.0.par_iter() @@ -101,7 +101,7 @@ impl<'a, T: CoordNum + Sync> IntoParallelIterator for &'a MultiPolygon { #[cfg(feature = "multithreading")] impl<'a, T: CoordNum + Send + Sync> IntoParallelIterator for &'a mut MultiPolygon { type Item = &'a mut Polygon; - type Iter = rayon::slice::IterMut<'a, Polygon>; // Parallel iterator type for a mutable slice of Polygon + type Iter = rayon::slice::IterMut<'a, Polygon>; fn into_par_iter(self) -> Self::Iter { self.0.par_iter_mut() From 2c2141da424319ad72a8c36591f1ac95c33e1c77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stephan=20H=C3=BCgel?= Date: Wed, 6 Nov 2024 20:38:49 +0000 Subject: [PATCH 6/8] Add some sanity / ergonomic tests for the multithreading impls --- geo-types/src/geometry/multi_line_string.rs | 14 ++++++++++++++ geo-types/src/geometry/multi_polygon.rs | 15 +++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/geo-types/src/geometry/multi_line_string.rs b/geo-types/src/geometry/multi_line_string.rs index 4fcdfa4666..957f8631df 100644 --- a/geo-types/src/geometry/multi_line_string.rs +++ b/geo-types/src/geometry/multi_line_string.rs @@ -231,6 +231,20 @@ mod test { use super::*; use crate::{line_string, wkt}; + #[test] + fn test_multithreading_linestring() { + let multi: MultiLineString = wkt! { + MULTILINESTRING((0 0,2 0,1 2,0 0), (10 10,12 10,11 12,10 10)) + }; + let mut multimut: MultiLineString = wkt! { + MULTILINESTRING((0 0,2 0,1 2,0 0), (10 10,12 10,11 12,10 10)) + }; + let _ = multi.par_iter().for_each(|_p| ()); + let _ = multimut.par_iter_mut().for_each(|_p| ()); + let _ = &multi.into_par_iter().for_each(|_p| ()); + let _ = &mut multimut.par_iter_mut().for_each(|_p| ()); + } + #[test] fn test_iter() { let multi: MultiLineString = wkt! { diff --git a/geo-types/src/geometry/multi_polygon.rs b/geo-types/src/geometry/multi_polygon.rs index 8ac2bf8c95..b5ca48be36 100644 --- a/geo-types/src/geometry/multi_polygon.rs +++ b/geo-types/src/geometry/multi_polygon.rs @@ -283,6 +283,21 @@ mod test { } } + #[test] + fn test_par_iter() { + let multi = MultiPolygon::new(vec![ + polygon![(x: 0, y: 0), (x: 2, y: 0), (x: 1, y: 2), (x:0, y:0)], + polygon![(x: 10, y: 10), (x: 12, y: 10), (x: 11, y: 12), (x:10, y:10)], + ]); + let mut multimut = MultiPolygon::new(vec![ + polygon![(x: 0, y: 0), (x: 2, y: 0), (x: 1, y: 2), (x:0, y:0)], + polygon![(x: 10, y: 10), (x: 12, y: 10), (x: 11, y: 12), (x:10, y:10)], + ]); + let _ = multi.par_iter().for_each(|_p| ()); + let _ = &multimut.par_iter_mut().for_each(|_p| ()); + let _ = &multi.into_par_iter().for_each(|_p| ()); + let _ = &mut multimut.par_iter_mut().for_each(|_p| ()); + } #[test] fn test_iter_mut() { let mut multi = MultiPolygon::new(vec![ From 7f6b16ab6ff266a2035a3ed2ad2613e2120d03e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stephan=20H=C3=BCgel?= Date: Wed, 6 Nov 2024 20:44:21 +0000 Subject: [PATCH 7/8] Feature-gate multithreading tests --- geo-types/src/geometry/multi_line_string.rs | 1 + geo-types/src/geometry/multi_polygon.rs | 1 + 2 files changed, 2 insertions(+) diff --git a/geo-types/src/geometry/multi_line_string.rs b/geo-types/src/geometry/multi_line_string.rs index 957f8631df..2bcbd85d7c 100644 --- a/geo-types/src/geometry/multi_line_string.rs +++ b/geo-types/src/geometry/multi_line_string.rs @@ -231,6 +231,7 @@ mod test { use super::*; use crate::{line_string, wkt}; + #[cfg(feature = "multithreading")] #[test] fn test_multithreading_linestring() { let multi: MultiLineString = wkt! { diff --git a/geo-types/src/geometry/multi_polygon.rs b/geo-types/src/geometry/multi_polygon.rs index b5ca48be36..6ec8bc03a2 100644 --- a/geo-types/src/geometry/multi_polygon.rs +++ b/geo-types/src/geometry/multi_polygon.rs @@ -283,6 +283,7 @@ mod test { } } + #[cfg(feature = "multithreading")] #[test] fn test_par_iter() { let multi = MultiPolygon::new(vec![ From 416b5c8f651e8452e554b5389a591d38a8d65aac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stephan=20H=C3=BCgel?= Date: Wed, 6 Nov 2024 20:49:08 +0000 Subject: [PATCH 8/8] Omit let bindings hated by Clippy --- geo-types/src/geometry/multi_line_string.rs | 4 ++-- geo-types/src/geometry/multi_polygon.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/geo-types/src/geometry/multi_line_string.rs b/geo-types/src/geometry/multi_line_string.rs index 2bcbd85d7c..ff19a7c3d4 100644 --- a/geo-types/src/geometry/multi_line_string.rs +++ b/geo-types/src/geometry/multi_line_string.rs @@ -240,8 +240,8 @@ mod test { let mut multimut: MultiLineString = wkt! { MULTILINESTRING((0 0,2 0,1 2,0 0), (10 10,12 10,11 12,10 10)) }; - let _ = multi.par_iter().for_each(|_p| ()); - let _ = multimut.par_iter_mut().for_each(|_p| ()); + multi.par_iter().for_each(|_p| ()); + multimut.par_iter_mut().for_each(|_p| ()); let _ = &multi.into_par_iter().for_each(|_p| ()); let _ = &mut multimut.par_iter_mut().for_each(|_p| ()); } diff --git a/geo-types/src/geometry/multi_polygon.rs b/geo-types/src/geometry/multi_polygon.rs index 6ec8bc03a2..0a68333b13 100644 --- a/geo-types/src/geometry/multi_polygon.rs +++ b/geo-types/src/geometry/multi_polygon.rs @@ -294,7 +294,7 @@ mod test { polygon![(x: 0, y: 0), (x: 2, y: 0), (x: 1, y: 2), (x:0, y:0)], polygon![(x: 10, y: 10), (x: 12, y: 10), (x: 11, y: 12), (x:10, y:10)], ]); - let _ = multi.par_iter().for_each(|_p| ()); + multi.par_iter().for_each(|_p| ()); let _ = &multimut.par_iter_mut().for_each(|_p| ()); let _ = &multi.into_par_iter().for_each(|_p| ()); let _ = &mut multimut.par_iter_mut().for_each(|_p| ());