From cf0fb0e57e5ad1e309e20e2d6b8719a1c0b0c694 Mon Sep 17 00:00:00 2001 From: Michael Kirk Date: Tue, 30 Apr 2024 13:17:07 -0700 Subject: [PATCH 1/2] attempt to make bench more deterministic Mostly the issue is with "bench HUGE polyline6 decoding". I'm not sure why. Interestingly this "HUGE" polyine is not much bigger than the other decode test, which seems to run in much more consistent time. --- benches/benchmarks.rs | 53 +++++++++++++++++++------------------------ 1 file changed, 23 insertions(+), 30 deletions(-) diff --git a/benches/benchmarks.rs b/benches/benchmarks.rs index 792e9e1..89097e7 100644 --- a/benches/benchmarks.rs +++ b/benches/benchmarks.rs @@ -1,73 +1,66 @@ #[macro_use] extern crate criterion; -use criterion::Criterion; +use criterion::{Criterion, black_box}; use geo_types::LineString; use polyline::{decode_polyline, encode_coordinates}; use rand::distributions::Distribution; use rand::distributions::Uniform; -use rand::thread_rng; +use rand::SeedableRng; +use rand::rngs::StdRng; + #[allow(unused_must_use)] fn bench_encode(c: &mut Criterion) { - let mut rng = thread_rng(); + let mut rng = StdRng::seed_from_u64(42); // These coordinates cover London, approximately let between_lon = Uniform::from(-6.379880..1.768960); let between_lat = Uniform::from(49.871159..55.811741); let mut v: Vec<[f64; 2]> = vec![]; - (0..1000).for_each(|_| v.push([between_lon.sample(&mut rng), between_lat.sample(&mut rng)])); + (0..10_000).for_each(|_| v.push([between_lon.sample(&mut rng), between_lat.sample(&mut rng)])); let res: LineString = v.into(); - c.bench_function("bench encode: 1000 coordinates", move |b| { + + c.bench_function("encode 10_000 coordinates at precision 1e-5", |b| { b.iter(|| { - encode_coordinates(res.clone(), 5); + black_box(encode_coordinates(res.coords().copied(), 5)); + }) + }); + + c.bench_function("encode 10_000 coordinates at precision 1e-6", |b| { + b.iter(|| { + black_box(encode_coordinates(res.coords().copied(), 6)); }) }); } #[allow(unused_must_use)] fn bench_decode(c: &mut Criterion) { - // comparable cpp (see e.g. Valhalla) decodes the same number of coords in around 500 µs - let mut rng = thread_rng(); + // comparable cpp (see e.g. Valhalla) + let mut rng = StdRng::seed_from_u64(42); // These coordinates cover London, approximately let between_lon = Uniform::from(-6.379880..1.768960); let between_lat = Uniform::from(49.871159..55.811741); let mut v: Vec<[f64; 2]> = vec![]; - (0..21501).for_each(|_| v.push([between_lon.sample(&mut rng), between_lat.sample(&mut rng)])); + (0..10_000).for_each(|_| v.push([between_lon.sample(&mut rng), between_lat.sample(&mut rng)])); let res: LineString = v.into(); let encoded = encode_coordinates(res, 6).unwrap(); - c.bench_function("bench decode: 21502 coordinates", move |b| { - b.iter(|| { - decode_polyline(&encoded, 6); - }) - }); -} -#[allow(unused_must_use)] -fn bench_polyline6_decoding(c: &mut Criterion) { - c.bench_function("bench polyline6 decoding", move |b| { + c.bench_function("decode 10_000 coordinates at precision 1e-5", |b| { b.iter(|| { - decode_polyline("_p~iF~ps|U_ulLnnqC_mqNvxq`@", 6).unwrap(); + black_box(decode_polyline(&encoded, 5)); }) }); -} -#[allow(unused_must_use)] -fn bench_polyline6_decoding_huge(c: &mut Criterion) { - c.bench_function("bench HUGE polyline6 decoding", move |b| { + c.bench_function("decode 10_000 coordinates at precision 1e-6", |b| { b.iter(|| { - decode_polyline( - include_str!("../resources/route-geometry-sweden-west-coast.polyline6"), - 6, - ) - .unwrap(); + black_box(decode_polyline(&encoded, 6)); }) }); } + criterion_group!( benches, bench_encode, bench_decode, - bench_polyline6_decoding, - bench_polyline6_decoding_huge ); criterion_main!(benches); From db9b5b90a512731178bc5b8c971f2e14283dc56f Mon Sep 17 00:00:00 2001 From: Michael Kirk Date: Tue, 30 Apr 2024 19:23:51 -0700 Subject: [PATCH 2/2] [perf] mutate rather than allocate a bunch of heap strings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bench output: encode 10_000 coordinates at precision 1e-5 time: [123.68 µs 123.80 µs 123.94 µs] change: [-74.528% -74.461% -74.401%] (p = 0.00 < 0.05) Performance has improved. Found 6 outliers among 100 measurements (6.00%) 4 (4.00%) high mild 2 (2.00%) high severe encode 10_000 coordinates at precision 1e-6 time: [136.60 µs 137.55 µs 138.55 µs] change: [-73.205% -73.066% -72.932%] (p = 0.00 < 0.05) Performance has improved. Found 11 outliers among 100 measurements (11.00%) 9 (9.00%) high mild 2 (2.00%) high severe --- CHANGELOG.md | 3 +++ src/lib.rs | 11 +++++------ 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 02f5aba..bdabad2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ ## Unreleased +* Speed up encode function (now runs in ~72% less time / 3.5x improvement): + * https://github.com/georust/polyline/pull/42 + ## 0.10.2 * fix decoder crashing with out-of-bounds error (https://github.com/georust/polyline/pull/37): diff --git a/src/lib.rs b/src/lib.rs index 96cfedc..c215abd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -46,14 +46,13 @@ where } } -fn encode(current: f64, previous: f64, factor: i32) -> Result { +fn encode(current: f64, previous: f64, factor: i32, output: &mut String) -> Result<(), String> { let current = scale(current, factor); let previous = scale(previous, factor); let mut coordinate = (current - previous) << 1; if (current - previous) < 0 { coordinate = !coordinate; } - let mut output: String = "".to_string(); while coordinate >= 0x20 { let from_char = char::from_u32(((0x20 | (coordinate & 0x1f)) + 63) as u32) .ok_or("Couldn't convert character")?; @@ -62,7 +61,7 @@ fn encode(current: f64, previous: f64, factor: i32) -> Result { } let from_char = char::from_u32((coordinate + 63) as u32).ok_or("Couldn't convert character")?; output.push(from_char); - Ok(output) + Ok(()) } /// Encodes a Google Encoded Polyline. @@ -83,7 +82,7 @@ where let base: i32 = 10; let factor: i32 = base.pow(precision); - let mut output = "".to_string(); + let mut output = String::new(); let mut b = Coord { x: 0.0, y: 0.0 }; for (i, a) in coordinates.into_iter().enumerate() { @@ -91,8 +90,8 @@ where .map_err(|e| format!("Latitude error at position {0}: {1}", i, e))?; check(a.x, (MIN_LONGITUDE, MAX_LONGITUDE)) .map_err(|e| format!("Longitude error at position {0}: {1}", i, e))?; - output = output + &encode(a.y, b.y, factor)?; - output = output + &encode(a.x, b.x, factor)?; + encode(a.y, b.y, factor, &mut output)?; + encode(a.x, b.x, factor, &mut output)?; b = a; } Ok(output)