Skip to content

Commit 5d2bfc2

Browse files
authored
Add Rust benchmarks (#349)
* format, benchmark, README.md * move rand into dev dependency
1 parent 16d7f99 commit 5d2bfc2

File tree

10 files changed

+144
-51
lines changed

10 files changed

+144
-51
lines changed

.travis.yml

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -119,8 +119,11 @@ matrix:
119119
# Rust implementation. Lives in rust/
120120
- language: rust
121121
env: OLC_PATH=rust
122+
before_script:
123+
- rustup component add rustfmt
122124
# Adding caching of .cargo doesn't improve the build/test times.
123125
script:
124126
- cd rust/
125-
- cargo build --verbose --all
126-
- cargo test --verbose --all
127+
- cargo fmt --all -- --check
128+
- cargo build
129+
- cargo test -- --nocapture

rust/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,6 @@ exclude = ["rust.iml"]
1010

1111
[dependencies]
1212
geo = "^0.4.3"
13+
14+
[dev-dependencies]
15+
rand = "^0.6.5"

rust/README.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
This is the Rust implementation of the Open Location Code library.
2+
3+
# Contributing
4+
5+
## Code Formatting
6+
7+
Code must be formatted with `rustfmt`. You can do this by running `cargo fmt`.
8+
9+
The formatting will be checked in the TravisCI integration tests. If the files
10+
need formatting the tests will fail.
11+
12+
## Testing
13+
14+
Test code by running `cargo test -- --nocapture`. This will run the tests
15+
including the benchmark loops.
16+

rust/src/codearea.rs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use geo::{Point};
1+
use geo::Point;
22

33
pub struct CodeArea {
44
pub south: f64,
@@ -12,9 +12,12 @@ pub struct CodeArea {
1212
impl CodeArea {
1313
pub fn new(south: f64, west: f64, north: f64, east: f64, code_length: usize) -> CodeArea {
1414
CodeArea {
15-
south: south, west: west, north: north, east: east,
15+
south: south,
16+
west: west,
17+
north: north,
18+
east: east,
1619
center: Point::new((west + east) / 2f64, (south + north) / 2f64),
17-
code_length: code_length
20+
code_length: code_length,
1821
}
1922
}
2023

@@ -24,8 +27,7 @@ impl CodeArea {
2427
self.west + other.west,
2528
self.north + other.north,
2629
self.east + other.east,
27-
self.code_length + other.code_length
30+
self.code_length + other.code_length,
2831
)
2932
}
3033
}
31-

rust/src/consts.rs

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,8 @@ pub const PADDING_CHAR_STR: &'static str = "0";
1010

1111
// The character set used to encode the values.
1212
pub const CODE_ALPHABET: [char; 20] = [
13-
'2', '3', '4', '5', '6',
14-
'7', '8', '9', 'C', 'F',
15-
'G', 'H', 'J', 'M', 'P',
16-
'Q', 'R', 'V', 'W', 'X',
13+
'2', '3', '4', '5', '6', '7', '8', '9', 'C', 'F', 'G', 'H', 'J', 'M', 'P', 'Q', 'R', 'V', 'W',
14+
'X',
1715
];
1816

1917
// The base to use to convert numbers to/from.
@@ -36,9 +34,7 @@ pub const MAX_CODE_LENGTH: usize = 15;
3634
// The resolution values in degrees for each position in the lat/lng pair
3735
// encoding. These give the place value of each position, and therefore the
3836
// dimensions of the resulting area.
39-
pub const PAIR_RESOLUTIONS: [f64; 5] = [
40-
20.0f64, 1.0f64, 0.05f64, 0.0025f64, 0.000125f64
41-
];
37+
pub const PAIR_RESOLUTIONS: [f64; 5] = [20.0f64, 1.0f64, 0.05f64, 0.0025f64, 0.000125f64];
4238

4339
// Number of columns in the grid refinement method.
4440
pub const GRID_COLUMNS: f64 = 4f64;

rust/src/interface.rs

Lines changed: 28 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,14 @@ use geo::Point;
33
use codearea::CodeArea;
44

55
use consts::{
6-
SEPARATOR, SEPARATOR_POSITION, PADDING_CHAR, PADDING_CHAR_STR, CODE_ALPHABET, ENCODING_BASE,
7-
LATITUDE_MAX, LONGITUDE_MAX, PAIR_CODE_LENGTH, MAX_CODE_LENGTH, PAIR_RESOLUTIONS, GRID_COLUMNS, GRID_ROWS,
8-
MIN_TRIMMABLE_CODE_LEN,
6+
CODE_ALPHABET, ENCODING_BASE, GRID_COLUMNS, GRID_ROWS, LATITUDE_MAX, LONGITUDE_MAX,
7+
MAX_CODE_LENGTH, MIN_TRIMMABLE_CODE_LEN, PADDING_CHAR, PADDING_CHAR_STR, PAIR_CODE_LENGTH,
8+
PAIR_RESOLUTIONS, SEPARATOR, SEPARATOR_POSITION,
99
};
1010

1111
use private::{
12-
code_value, normalize_longitude, clip_latitude, compute_latitude_precision, prefix_by_reference,
13-
narrow_region,
12+
clip_latitude, code_value, compute_latitude_precision, narrow_region, normalize_longitude,
13+
prefix_by_reference,
1414
};
1515

1616
/// Determines if a code is a valid Open Location Code.
@@ -62,7 +62,7 @@ pub fn is_valid(_code: &str) -> bool {
6262
return false;
6363
}
6464
// Extract the padding from the code (mutates code)
65-
let padding: String = code.drain(ppos..eppos+1).collect();
65+
let padding: String = code.drain(ppos..eppos + 1).collect();
6666
if padding.chars().any(|c| c != PADDING_CHAR) {
6767
// Padding must be one, contiguous block of padding chars
6868
return false;
@@ -80,8 +80,7 @@ pub fn is_valid(_code: &str) -> bool {
8080
/// A short Open Location Code is a sequence created by removing four or more
8181
/// digits from an Open Location Code. It must include a separator character.
8282
pub fn is_short(_code: &str) -> bool {
83-
is_valid(_code) &&
84-
_code.find(SEPARATOR).unwrap() < SEPARATOR_POSITION
83+
is_valid(_code) && _code.find(SEPARATOR).unwrap() < SEPARATOR_POSITION
8584
}
8685

8786
/// Determines if a code is a valid full Open Location Code.
@@ -143,9 +142,7 @@ pub fn encode(pt: Point<f64>, code_length: usize) -> String {
143142
}
144143
}
145144
if digit < SEPARATOR_POSITION {
146-
code.push_str(
147-
PADDING_CHAR_STR.repeat(SEPARATOR_POSITION - digit).as_str()
148-
);
145+
code.push_str(PADDING_CHAR_STR.repeat(SEPARATOR_POSITION - digit).as_str());
149146
code.push(SEPARATOR);
150147
}
151148
code
@@ -159,7 +156,8 @@ pub fn decode(_code: &str) -> Result<CodeArea, String> {
159156
if !is_full(_code) {
160157
return Err(format!("Code must be a valid full code: {}", _code));
161158
}
162-
let mut code = _code.to_string()
159+
let mut code = _code
160+
.to_string()
163161
.replace(SEPARATOR, "")
164162
.replace(PADDING_CHAR_STR, "")
165163
.to_uppercase();
@@ -189,7 +187,13 @@ pub fn decode(_code: &str) -> Result<CodeArea, String> {
189187
lng += lng_res * (code_value(chr) as f64 % GRID_COLUMNS);
190188
}
191189
}
192-
Ok(CodeArea::new(lat, lng, lat + lat_res, lng + lng_res, code.len()))
190+
Ok(CodeArea::new(
191+
lat,
192+
lng,
193+
lat + lat_res,
194+
lng + lng_res,
195+
code.len(),
196+
))
193197
}
194198

195199
/// Remove characters from the start of an OLC code.
@@ -218,13 +222,16 @@ pub fn shorten(_code: &str, ref_pt: Point<f64>) -> Result<String, String> {
218222

219223
let codearea: CodeArea = decode(_code).unwrap();
220224
if codearea.code_length < MIN_TRIMMABLE_CODE_LEN {
221-
return Err(format!("Code length must be at least {}", MIN_TRIMMABLE_CODE_LEN));
225+
return Err(format!(
226+
"Code length must be at least {}",
227+
MIN_TRIMMABLE_CODE_LEN
228+
));
222229
}
223230

224231
// How close are the latitude and longitude to the code center.
225-
let range = (codearea.center.lat() - clip_latitude(ref_pt.lat())).abs().max(
226-
(codearea.center.lng() - normalize_longitude(ref_pt.lng())).abs()
227-
);
232+
let range = (codearea.center.lat() - clip_latitude(ref_pt.lat()))
233+
.abs()
234+
.max((codearea.center.lng() - normalize_longitude(ref_pt.lng())).abs());
228235

229236
for i in 0..PAIR_RESOLUTIONS.len() - 2 {
230237
// Check if we're close enough to shorten. The range must be less than 1/2
@@ -299,6 +306,8 @@ pub fn recover_nearest(_code: &str, ref_pt: Point<f64>) -> Result<String, String
299306
} else if ref_lng - half_res > longitude {
300307
longitude += resolution;
301308
}
302-
Ok(encode(Point::new(longitude, latitude), code_area.code_length))
309+
Ok(encode(
310+
Point::new(longitude, latitude),
311+
code_area.code_length,
312+
))
303313
}
304-

rust/src/lib.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,4 @@ mod codearea;
77
pub use codearea::CodeArea;
88

99
mod interface;
10-
pub use interface::{is_valid, is_short, is_full, encode, decode, shorten, recover_nearest};
11-
10+
pub use interface::{decode, encode, is_full, is_short, is_valid, recover_nearest, shorten};

rust/src/private.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use consts::{
2-
CODE_ALPHABET, ENCODING_BASE, LATITUDE_MAX, LONGITUDE_MAX, PAIR_CODE_LENGTH, GRID_ROWS,
3-
GRID_COLUMNS, NARROW_REGION_PRECISION,
2+
CODE_ALPHABET, ENCODING_BASE, GRID_COLUMNS, GRID_ROWS, LATITUDE_MAX, LONGITUDE_MAX,
3+
NARROW_REGION_PRECISION, PAIR_CODE_LENGTH,
44
};
55

66
use interface::encode;
@@ -19,7 +19,7 @@ pub fn normalize_longitude(value: f64) -> f64 {
1919
while result >= LONGITUDE_MAX {
2020
result -= LONGITUDE_MAX * 2f64;
2121
}
22-
while result < -LONGITUDE_MAX{
22+
while result < -LONGITUDE_MAX {
2323
result += LONGITUDE_MAX * 2f64;
2424
}
2525
result
@@ -31,7 +31,7 @@ pub fn clip_latitude(latitude_degrees: f64) -> f64 {
3131

3232
pub fn compute_latitude_precision(code_length: usize) -> f64 {
3333
if code_length <= PAIR_CODE_LENGTH {
34-
return ENCODING_BASE.powf((code_length as f64 / -2f64 + 2f64).floor())
34+
return ENCODING_BASE.powf((code_length as f64 / -2f64 + 2f64).floor());
3535
}
3636
ENCODING_BASE.powi(-3i32) / GRID_ROWS.powf(code_length as f64 - PAIR_CODE_LENGTH as f64)
3737
}
@@ -41,9 +41,9 @@ pub fn prefix_by_reference(pt: Point<f64>, code_length: usize) -> String {
4141
let mut code = encode(
4242
Point::new(
4343
(pt.lng() / precision).floor() * precision,
44-
(pt.lat() / precision).floor() * precision
44+
(pt.lat() / precision).floor() * precision,
4545
),
46-
PAIR_CODE_LENGTH
46+
PAIR_CODE_LENGTH,
4747
);
4848
code.drain(code_length..);
4949
code

rust/tests/all_test.rs

Lines changed: 74 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
1-
extern crate open_location_code;
21
extern crate geo;
2+
extern crate open_location_code;
3+
extern crate rand;
34

5+
use rand::Rng;
6+
use std::time::Instant;
47
use std::vec::Vec;
58

6-
use open_location_code::{is_valid, is_short, is_full};
7-
use open_location_code::{encode, decode};
8-
use open_location_code::{shorten, recover_nearest};
9+
use open_location_code::{decode, encode};
10+
use open_location_code::{is_full, is_short, is_valid};
11+
use open_location_code::{recover_nearest, shorten};
912

1013
use geo::Point;
1114

@@ -77,9 +80,12 @@ fn encode_test() {
7780
let code = cols[3];
7881

7982
assert_eq!(
80-
encode(Point::new(lng, lat), len), code,
83+
encode(Point::new(lng, lat), len),
84+
code,
8185
"encoding lat={},lng={},len={}",
82-
lat, lng, len
86+
lat,
87+
lng,
88+
len
8389
);
8490

8591
tested += 1;
@@ -99,7 +105,11 @@ fn shorten_recovery_test() {
99105
let test_type = cols[4];
100106

101107
if test_type == "B" || test_type == "S" {
102-
assert_eq!(shorten(full_code, Point::new(lng, lat)).unwrap(), short_code, "shorten");
108+
assert_eq!(
109+
shorten(full_code, Point::new(lng, lat)).unwrap(),
110+
short_code,
111+
"shorten"
112+
);
103113
}
104114
if test_type == "B" || test_type == "R" {
105115
assert_eq!(
@@ -111,7 +121,63 @@ fn shorten_recovery_test() {
111121

112122
tested += 1;
113123
}
114-
115124
assert!(tested > 0);
116125
}
117126

127+
#[test]
128+
fn benchmark_test() {
129+
struct BenchmarkData {
130+
lat: f64,
131+
lng: f64,
132+
len: usize,
133+
}
134+
let mut rng = rand::thread_rng();
135+
// Create the benchmark data - coordinates and lengths for encoding, codes for decoding.
136+
let loops = 100000;
137+
let mut bd: Vec<BenchmarkData> = Vec::new();
138+
for _i in 0..loops {
139+
let lat = rng.gen_range(-90.0, 90.0);
140+
let lng = rng.gen_range(-180.0, 180.0);
141+
let mut len = rng.gen_range(2, 15);
142+
// Make sure the length is even if it's less than 10.
143+
if len < 10 && len % 2 == 1 {
144+
len += 1;
145+
}
146+
let b = BenchmarkData {
147+
lat: lat,
148+
lng: lng,
149+
len: len,
150+
};
151+
bd.push(b);
152+
}
153+
154+
// Do the encode benchmark.
155+
// Get the current time, loop through the benchmark data, print the time.
156+
let mut codes: Vec<String> = Vec::new();
157+
let mut now = Instant::now();
158+
for b in &bd {
159+
codes.push(encode(Point::new(b.lng, b.lat), b.len));
160+
}
161+
let enc_duration = now.elapsed().as_secs() * 1000000 + now.elapsed().subsec_micros() as u64;
162+
163+
// Do the encode benchmark.
164+
// Get the current time, loop through the benchmark data, print the time.
165+
now = Instant::now();
166+
for c in codes {
167+
let _c = decode(&c);
168+
}
169+
let dec_duration = now.elapsed().as_secs() * 1000000 + now.elapsed().subsec_micros() as u64;
170+
// Output.
171+
println!(
172+
"Encoding benchmark: {} loops, total time {} usec, {} usec per encode",
173+
loops,
174+
enc_duration,
175+
enc_duration as f64 / loops as f64
176+
);
177+
println!(
178+
"Decoding benchmark: {} loops, total time {} usec, {} usec per decode",
179+
loops,
180+
dec_duration,
181+
dec_duration as f64 / loops as f64
182+
);
183+
}

rust/tests/csv_reader.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use std::fs::File;
33
use std::io::{BufRead, BufReader, Lines};
44

55
pub struct CSVReader {
6-
iter: Lines<BufReader<File>>
6+
iter: Lines<BufReader<File>>,
77
}
88

99
impl CSVReader {
@@ -32,4 +32,3 @@ impl Iterator for CSVReader {
3232
None
3333
}
3434
}
35-

0 commit comments

Comments
 (0)