Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
53 changes: 23 additions & 30 deletions benches/benchmarks.rs
Original file line number Diff line number Diff line change
@@ -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<f64> = 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<f64> = 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);
11 changes: 5 additions & 6 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,14 +46,13 @@ where
}
}

fn encode(current: f64, previous: f64, factor: i32) -> Result<String, String> {
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();

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

This allocation goes away

while coordinate >= 0x20 {
let from_char = char::from_u32(((0x20 | (coordinate & 0x1f)) + 63) as u32)
.ok_or("Couldn't convert character")?;
Expand All @@ -62,7 +61,7 @@ fn encode(current: f64, previous: f64, factor: i32) -> Result<String, String> {
}
let from_char = char::from_u32((coordinate + 63) as u32).ok_or("Couldn't convert character")?;
output.push(from_char);

@michaelkirk michaelkirk May 1, 2024

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

And we now just push directly onto the passed in string. Saving an allocation and a concatenation.

Ok(output)
Ok(())
}

/// Encodes a Google Encoded Polyline.
Expand All @@ -83,16 +82,16 @@ 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() {
check(a.y, (MIN_LATITUDE, MAX_LATITUDE))
.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)
Expand Down