#![cfg(all(feature = "bench-spatialite", not(target_arch = "wasm32")))]
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion, Throughput};
use libsqlite3_sys::*;
use std::ffi::{CStr, CString};
use std::hint::black_box;
use std::ptr;
const SPATIALITE_LIB: &str = "mod_spatialite";
const N_POINTS: usize = 50_000;
const RNG_SEED: u64 = 0xC0FFEE;
unsafe fn open_sqlitegis_db() -> *mut sqlite3 {
unsafe {
let mut db = ptr::null_mut();
let memdb = CString::new(":memory:").unwrap();
assert_eq!(
sqlite3_open(memdb.as_ptr(), &mut db),
SQLITE_OK,
"sqlite3_open failed"
);
let rc = sqlitegis::sqlite::register_functions(db);
assert_eq!(rc, SQLITE_OK, "register_functions failed (rc={rc})");
seed(db, Kind::Sqlitegis);
db
}
}
unsafe fn open_spatialite_db() -> *mut sqlite3 {
unsafe {
let mut db = ptr::null_mut();
let memdb = CString::new(":memory:").unwrap();
assert_eq!(
sqlite3_open(memdb.as_ptr(), &mut db),
SQLITE_OK,
"sqlite3_open failed"
);
assert_eq!(
sqlite3_enable_load_extension(db, 1),
SQLITE_OK,
"enable_load_extension failed"
);
let cpath = CString::new(SPATIALITE_LIB).unwrap();
let mut err: *mut std::os::raw::c_char = ptr::null_mut();
let rc = sqlite3_load_extension(db, cpath.as_ptr(), ptr::null(), &mut err);
if rc != SQLITE_OK {
let msg = if err.is_null() {
"(no message)".to_string()
} else {
CStr::from_ptr(err).to_string_lossy().into_owned()
};
panic!("load_extension({SPATIALITE_LIB}) failed: rc={rc} err={msg}");
}
exec(db, "SELECT InitSpatialMetaData(1)");
seed(db, Kind::Spatialite);
db
}
}
unsafe fn exec(db: *mut sqlite3, sql: &str) {
unsafe {
let csql = CString::new(sql).unwrap();
let mut err: *mut std::os::raw::c_char = ptr::null_mut();
let rc = sqlite3_exec(db, csql.as_ptr(), None, ptr::null_mut(), &mut err);
if rc != SQLITE_OK {
let msg = if err.is_null() {
"(no message)".to_string()
} else {
CStr::from_ptr(err).to_string_lossy().into_owned()
};
sqlite3_free(err.cast());
panic!("exec failed (rc={rc}): {sql}: {msg}");
}
if !err.is_null() {
sqlite3_free(err.cast());
}
}
}
unsafe fn query_count(db: *mut sqlite3, sql: &str) -> i64 {
unsafe {
let csql = CString::new(sql).unwrap();
let mut stmt = ptr::null_mut();
let rc = sqlite3_prepare_v2(db, csql.as_ptr(), -1, &mut stmt, ptr::null_mut());
if rc != SQLITE_OK {
let msg = CStr::from_ptr(sqlite3_errmsg(db)).to_string_lossy();
panic!("prepare failed (rc={rc}): {sql}: {msg}");
}
let step_rc = sqlite3_step(stmt);
assert_eq!(step_rc, SQLITE_ROW, "expected a row from: {sql}");
let v = sqlite3_column_int64(stmt, 0);
sqlite3_finalize(stmt);
v
}
}
#[derive(Copy, Clone)]
enum Kind {
Sqlitegis,
Spatialite,
}
unsafe fn seed(db: *mut sqlite3, kind: Kind) {
unsafe {
match kind {
Kind::Sqlitegis => {
exec(
db,
"CREATE TABLE places (id INTEGER PRIMARY KEY, geom BLOB)",
);
exec(
db,
"CREATE TABLE regions (id INTEGER PRIMARY KEY, geom BLOB)",
);
}
Kind::Spatialite => {
exec(db, "CREATE TABLE places (id INTEGER PRIMARY KEY)");
exec(
db,
"SELECT AddGeometryColumn('places', 'geom', 4326, 'POINT', 'XY')",
);
exec(db, "CREATE TABLE regions (id INTEGER PRIMARY KEY)");
exec(
db,
"SELECT AddGeometryColumn('regions', 'geom', 4326, 'POLYGON', 'XY')",
);
}
}
let mut state: u64 = RNG_SEED;
exec(db, "BEGIN");
let insert_sql =
CString::new("INSERT INTO places(geom) VALUES (ST_GeomFromText(?, 4326))").unwrap();
let mut stmt = ptr::null_mut();
assert_eq!(
sqlite3_prepare_v2(db, insert_sql.as_ptr(), -1, &mut stmt, ptr::null_mut()),
SQLITE_OK,
);
for _ in 0..N_POINTS {
let (x, y) = next_xy(&mut state);
let wkt = format!("POINT({x} {y})");
let cwkt = CString::new(wkt).unwrap();
sqlite3_bind_text(stmt, 1, cwkt.as_ptr(), -1, SQLITE_TRANSIENT());
let step = sqlite3_step(stmt);
assert_eq!(step, SQLITE_DONE, "INSERT step rc {step}");
sqlite3_reset(stmt);
}
sqlite3_finalize(stmt);
let insert_poly_sql =
CString::new("INSERT INTO regions(geom) VALUES (ST_GeomFromText(?, 4326))").unwrap();
let mut pstmt = ptr::null_mut();
assert_eq!(
sqlite3_prepare_v2(
db,
insert_poly_sql.as_ptr(),
-1,
&mut pstmt,
ptr::null_mut()
),
SQLITE_OK,
);
const HALF: f64 = 0.05;
for _ in 0..N_POINTS {
let (cx, cy) = next_xy(&mut state);
let (x0, y0, x1, y1) = (cx - HALF, cy - HALF, cx + HALF, cy + HALF);
let wkt = format!("POLYGON(({x0} {y0},{x1} {y0},{x1} {y1},{x0} {y1},{x0} {y0}))");
let cwkt = CString::new(wkt).unwrap();
sqlite3_bind_text(pstmt, 1, cwkt.as_ptr(), -1, SQLITE_TRANSIENT());
let step = sqlite3_step(pstmt);
assert_eq!(step, SQLITE_DONE, "regions INSERT step rc {step}");
sqlite3_reset(pstmt);
}
sqlite3_finalize(pstmt);
exec(db, "COMMIT");
}
}
fn next_xy(state: &mut u64) -> (f64, f64) {
let x = {
*state = state.wrapping_mul(1_664_525).wrapping_add(1_013_904_223);
((*state >> 33) as f64) / (u32::MAX as f64) * 360.0 - 180.0
};
let y = {
*state = state.wrapping_mul(1_664_525).wrapping_add(1_013_904_223);
((*state >> 33) as f64) / (u32::MAX as f64) * 180.0 - 90.0
};
(x, y)
}
fn bench_bulk_intersects_unindexed(c: &mut Criterion) {
let db_g = unsafe { open_sqlitegis_db() };
let db_s = unsafe { open_spatialite_db() };
let window = "POLYGON((10 20, 11 20, 11 21, 10 21, 10 20))";
let sql = format!(
"SELECT COUNT(*) FROM places WHERE ST_Intersects(geom, ST_GeomFromText('{window}', 4326))"
);
let mut group = c.benchmark_group("Unindexed ST_Intersects bulk");
group.throughput(Throughput::Elements(N_POINTS as u64));
group.bench_function(BenchmarkId::new("sqlitegis", N_POINTS), |b| {
b.iter(|| unsafe { black_box(query_count(db_g, &sql)) })
});
group.bench_function(BenchmarkId::new("spatialite", N_POINTS), |b| {
b.iter(|| unsafe { black_box(query_count(db_s, &sql)) })
});
group.finish();
unsafe {
sqlite3_close(db_g);
sqlite3_close(db_s);
}
}
fn bench_indexed_intersects(c: &mut Criterion) {
let db_g = unsafe {
let db = open_sqlitegis_db();
exec(db, "SELECT CreateSpatialIndex('places', 'geom')");
db
};
let db_s = unsafe {
let db = open_spatialite_db();
exec(db, "SELECT CreateSpatialIndex('places', 'geom')");
db
};
let window = "POLYGON((10 20, 11 20, 11 21, 10 21, 10 20))";
let sql_g = format!(
"SELECT COUNT(*) FROM places p \
JOIN places_geom_rtree r ON p.rowid = r.id \
WHERE r.xmin <= 11 AND r.xmax >= 10 AND r.ymin <= 21 AND r.ymax >= 20 \
AND ST_Intersects(p.geom, ST_GeomFromText('{window}', 4326))"
);
let sql_s = format!(
"SELECT COUNT(*) FROM places p \
JOIN idx_places_geom r ON p.rowid = r.pkid \
WHERE r.xmin <= 11 AND r.xmax >= 10 AND r.ymin <= 21 AND r.ymax >= 20 \
AND ST_Intersects(p.geom, ST_GeomFromText('{window}', 4326))"
);
let mut group = c.benchmark_group("Indexed ST_Intersects window");
group.throughput(Throughput::Elements(N_POINTS as u64));
group.bench_function(BenchmarkId::new("sqlitegis", N_POINTS), |b| {
b.iter(|| unsafe { black_box(query_count(db_g, &sql_g)) })
});
group.bench_function(BenchmarkId::new("spatialite", N_POINTS), |b| {
b.iter(|| unsafe { black_box(query_count(db_s, &sql_s)) })
});
group.finish();
unsafe {
sqlite3_close(db_g);
sqlite3_close(db_s);
}
}
fn bench_distance_sphere(c: &mut Criterion) {
let db_g = unsafe { open_sqlitegis_db() };
let db_s = unsafe { open_spatialite_db() };
let sql_g = "SELECT COUNT(*) FROM places \
WHERE ST_DistanceSphere(geom, ST_GeomFromText('POINT(0 0)', 4326)) < 1000000.0";
let sql_s = "SELECT COUNT(*) FROM places \
WHERE ST_Distance(geom, ST_GeomFromText('POINT(0 0)', 4326), 0) < 1000000.0";
let mut group = c.benchmark_group("Geodesic distance bulk");
group.throughput(Throughput::Elements(N_POINTS as u64));
group.bench_function(BenchmarkId::new("sqlitegis", N_POINTS), |b| {
b.iter(|| unsafe { black_box(query_count(db_g, sql_g)) })
});
group.bench_function(BenchmarkId::new("spatialite", N_POINTS), |b| {
b.iter(|| unsafe { black_box(query_count(db_s, sql_s)) })
});
group.finish();
unsafe {
sqlite3_close(db_g);
sqlite3_close(db_s);
}
}
fn bench_astext_throughput(c: &mut Criterion) {
let db_g = unsafe { open_sqlitegis_db() };
let db_s = unsafe { open_spatialite_db() };
let sql = "SELECT COUNT(ST_AsText(geom)) FROM places";
let mut group = c.benchmark_group("ST_AsText scalar throughput");
group.throughput(Throughput::Elements(N_POINTS as u64));
group.bench_function(BenchmarkId::new("sqlitegis", N_POINTS), |b| {
b.iter(|| unsafe { black_box(query_count(db_g, sql)) })
});
group.bench_function(BenchmarkId::new("spatialite", N_POINTS), |b| {
b.iter(|| unsafe { black_box(query_count(db_s, sql)) })
});
group.finish();
unsafe {
sqlite3_close(db_g);
sqlite3_close(db_s);
}
}
fn bench_buffer_intersection(c: &mut Criterion) {
let db_g = unsafe { open_sqlitegis_db() };
let db_s = unsafe { open_spatialite_db() };
let sql = "SELECT COUNT(*) FROM places \
WHERE NOT ST_IsEmpty(ST_Intersection( \
ST_Buffer(ST_GeomFromText('POLYGON((10 20, 11 20, 11 21, 10 21, 10 20))', 4326), 0.1), \
geom \
))";
let mut group = c.benchmark_group("ST_Buffer + ST_Intersection bulk");
group.throughput(Throughput::Elements(N_POINTS as u64));
group.bench_function(BenchmarkId::new("sqlitegis", N_POINTS), |b| {
b.iter(|| unsafe { black_box(query_count(db_g, sql)) })
});
group.bench_function(BenchmarkId::new("spatialite", N_POINTS), |b| {
b.iter(|| unsafe { black_box(query_count(db_s, sql)) })
});
group.finish();
unsafe {
sqlite3_close(db_g);
sqlite3_close(db_s);
}
}
fn bench_bulk_contains_unindexed(c: &mut Criterion) {
let db_g = unsafe { open_sqlitegis_db() };
let db_s = unsafe { open_spatialite_db() };
let sql = "SELECT COUNT(*) FROM places \
WHERE ST_Contains(ST_GeomFromText('POLYGON((0 0, 36 0, 36 18, 0 18, 0 0))', 4326), geom)";
let mut group = c.benchmark_group("Unindexed ST_Contains bulk");
group.throughput(Throughput::Elements(N_POINTS as u64));
group.bench_function(BenchmarkId::new("sqlitegis", N_POINTS), |b| {
b.iter(|| unsafe { black_box(query_count(db_g, sql)) })
});
group.bench_function(BenchmarkId::new("spatialite", N_POINTS), |b| {
b.iter(|| unsafe { black_box(query_count(db_s, sql)) })
});
group.finish();
unsafe {
sqlite3_close(db_g);
sqlite3_close(db_s);
}
}
fn bench_indexed_contains(c: &mut Criterion) {
let db_g = unsafe {
let db = open_sqlitegis_db();
exec(db, "SELECT CreateSpatialIndex('places', 'geom')");
db
};
let db_s = unsafe {
let db = open_spatialite_db();
exec(db, "SELECT CreateSpatialIndex('places', 'geom')");
db
};
let sql_g = "SELECT COUNT(*) FROM places p \
JOIN places_geom_rtree r ON p.rowid = r.id \
WHERE r.xmin >= 0 AND r.xmax <= 36 AND r.ymin >= 0 AND r.ymax <= 18 \
AND ST_Contains(ST_GeomFromText('POLYGON((0 0, 36 0, 36 18, 0 18, 0 0))', 4326), p.geom)";
let sql_s = "SELECT COUNT(*) FROM places p \
JOIN idx_places_geom r ON p.rowid = r.pkid \
WHERE r.xmin >= 0 AND r.xmax <= 36 AND r.ymin >= 0 AND r.ymax <= 18 \
AND ST_Contains(ST_GeomFromText('POLYGON((0 0, 36 0, 36 18, 0 18, 0 0))', 4326), p.geom)";
let mut group = c.benchmark_group("Indexed ST_Contains window");
group.throughput(Throughput::Elements(N_POINTS as u64));
group.bench_function(BenchmarkId::new("sqlitegis", N_POINTS), |b| {
b.iter(|| unsafe { black_box(query_count(db_g, sql_g)) })
});
group.bench_function(BenchmarkId::new("spatialite", N_POINTS), |b| {
b.iter(|| unsafe { black_box(query_count(db_s, sql_s)) })
});
group.finish();
unsafe {
sqlite3_close(db_g);
sqlite3_close(db_s);
}
}
fn bench_buffer_throughput(c: &mut Criterion) {
let db_g = unsafe { open_sqlitegis_db() };
let db_s = unsafe { open_spatialite_db() };
let sql = "SELECT SUM(ST_Area(ST_Buffer(geom, 0.01))) FROM places";
let mut group = c.benchmark_group("ST_Buffer scalar throughput");
group.throughput(Throughput::Elements(N_POINTS as u64));
group.bench_function(BenchmarkId::new("sqlitegis", N_POINTS), |b| {
b.iter(|| unsafe { black_box(query_count(db_g, sql)) })
});
group.bench_function(BenchmarkId::new("spatialite", N_POINTS), |b| {
b.iter(|| unsafe { black_box(query_count(db_s, sql)) })
});
group.finish();
unsafe {
sqlite3_close(db_g);
sqlite3_close(db_s);
}
}
fn bench_centroid_throughput(c: &mut Criterion) {
let db_g = unsafe { open_sqlitegis_db() };
let db_s = unsafe { open_spatialite_db() };
let sql = "SELECT COUNT(ST_Centroid(geom)) FROM regions";
let mut group = c.benchmark_group("ST_Centroid scalar throughput");
group.throughput(Throughput::Elements(N_POINTS as u64));
group.bench_function(BenchmarkId::new("sqlitegis", N_POINTS), |b| {
b.iter(|| unsafe { black_box(query_count(db_g, sql)) })
});
group.bench_function(BenchmarkId::new("spatialite", N_POINTS), |b| {
b.iter(|| unsafe { black_box(query_count(db_s, sql)) })
});
group.finish();
unsafe {
sqlite3_close(db_g);
sqlite3_close(db_s);
}
}
fn bench_difference_disjoint(c: &mut Criterion) {
let db_g = unsafe { open_sqlitegis_db() };
let db_s = unsafe { open_spatialite_db() };
let sql = "SELECT COUNT(*) FROM regions \
WHERE NOT ST_IsEmpty(ST_Difference( \
geom, \
ST_GeomFromText('POLYGON((-179.9 -89.9, -179 -89.9, -179 -89, -179.9 -89, -179.9 -89.9))', 4326) \
))";
let mut group = c.benchmark_group("ST_Difference disjoint bulk");
group.throughput(Throughput::Elements(N_POINTS as u64));
group.bench_function(BenchmarkId::new("sqlitegis", N_POINTS), |b| {
b.iter(|| unsafe { black_box(query_count(db_g, sql)) })
});
group.bench_function(BenchmarkId::new("spatialite", N_POINTS), |b| {
b.iter(|| unsafe { black_box(query_count(db_s, sql)) })
});
group.finish();
unsafe {
sqlite3_close(db_g);
sqlite3_close(db_s);
}
}
fn bench_bulk_covers_unindexed(c: &mut Criterion) {
let db_g = unsafe { open_sqlitegis_db() };
let db_s = unsafe { open_spatialite_db() };
let sql = "SELECT COUNT(*) FROM places \
WHERE ST_Covers(ST_GeomFromText('POLYGON((0 0, 36 0, 36 18, 0 18, 0 0))', 4326), geom)";
let mut group = c.benchmark_group("Unindexed ST_Covers bulk");
group.throughput(Throughput::Elements(N_POINTS as u64));
group.bench_function(BenchmarkId::new("sqlitegis", N_POINTS), |b| {
b.iter(|| unsafe { black_box(query_count(db_g, sql)) })
});
group.bench_function(BenchmarkId::new("spatialite", N_POINTS), |b| {
b.iter(|| unsafe { black_box(query_count(db_s, sql)) })
});
group.finish();
unsafe {
sqlite3_close(db_g);
sqlite3_close(db_s);
}
}
fn bench_bulk_touches_unindexed(c: &mut Criterion) {
let db_g = unsafe { open_sqlitegis_db() };
let db_s = unsafe { open_spatialite_db() };
let sql = "SELECT COUNT(*) FROM regions \
WHERE ST_Touches(geom, ST_GeomFromText('POLYGON((10 20, 11 20, 11 21, 10 21, 10 20))', 4326))";
let mut group = c.benchmark_group("Unindexed ST_Touches bulk");
group.throughput(Throughput::Elements(N_POINTS as u64));
group.bench_function(BenchmarkId::new("sqlitegis", N_POINTS), |b| {
b.iter(|| unsafe { black_box(query_count(db_g, sql)) })
});
group.bench_function(BenchmarkId::new("spatialite", N_POINTS), |b| {
b.iter(|| unsafe { black_box(query_count(db_s, sql)) })
});
group.finish();
unsafe {
sqlite3_close(db_g);
sqlite3_close(db_s);
}
}
fn bench_bulk_overlaps_unindexed(c: &mut Criterion) {
let db_g = unsafe { open_sqlitegis_db() };
let db_s = unsafe { open_spatialite_db() };
let sql = "SELECT COUNT(*) FROM regions \
WHERE ST_Overlaps(geom, ST_GeomFromText('POLYGON((10 20, 11 20, 11 21, 10 21, 10 20))', 4326))";
let mut group = c.benchmark_group("Unindexed ST_Overlaps bulk");
group.throughput(Throughput::Elements(N_POINTS as u64));
group.bench_function(BenchmarkId::new("sqlitegis", N_POINTS), |b| {
b.iter(|| unsafe { black_box(query_count(db_g, sql)) })
});
group.bench_function(BenchmarkId::new("spatialite", N_POINTS), |b| {
b.iter(|| unsafe { black_box(query_count(db_s, sql)) })
});
group.finish();
unsafe {
sqlite3_close(db_g);
sqlite3_close(db_s);
}
}
fn bench_bulk_equals_unindexed(c: &mut Criterion) {
let db_g = unsafe { open_sqlitegis_db() };
let db_s = unsafe { open_spatialite_db() };
let sql = "SELECT COUNT(*) FROM regions \
WHERE ST_Equals(geom, ST_GeomFromText('POLYGON((10 20, 11 20, 11 21, 10 21, 10 20))', 4326))";
let mut group = c.benchmark_group("Unindexed ST_Equals bulk");
group.throughput(Throughput::Elements(N_POINTS as u64));
group.bench_function(BenchmarkId::new("sqlitegis", N_POINTS), |b| {
b.iter(|| unsafe { black_box(query_count(db_g, sql)) })
});
group.bench_function(BenchmarkId::new("spatialite", N_POINTS), |b| {
b.iter(|| unsafe { black_box(query_count(db_s, sql)) })
});
group.finish();
unsafe {
sqlite3_close(db_g);
sqlite3_close(db_s);
}
}
fn bench_bulk_dwithin_unindexed(c: &mut Criterion) {
let db_g = unsafe { open_sqlitegis_db() };
let db_s = unsafe { open_spatialite_db() };
let sql_g = "SELECT COUNT(*) FROM places \
WHERE ST_DWithin(geom, ST_GeomFromText('POINT(0 0)', 4326), 5.0)";
let sql_s = "SELECT COUNT(*) FROM places \
WHERE ST_Distance(geom, ST_GeomFromText('POINT(0 0)', 4326)) <= 5.0";
let mut group = c.benchmark_group("Unindexed ST_DWithin bulk");
group.throughput(Throughput::Elements(N_POINTS as u64));
group.bench_function(BenchmarkId::new("sqlitegis", N_POINTS), |b| {
b.iter(|| unsafe { black_box(query_count(db_g, sql_g)) })
});
group.bench_function(BenchmarkId::new("spatialite", N_POINTS), |b| {
b.iter(|| unsafe { black_box(query_count(db_s, sql_s)) })
});
group.finish();
unsafe {
sqlite3_close(db_g);
sqlite3_close(db_s);
}
}
fn bench_union_disjoint(c: &mut Criterion) {
let db_g = unsafe { open_sqlitegis_db() };
let db_s = unsafe { open_spatialite_db() };
let sql = "SELECT COUNT(*) FROM regions \
WHERE NOT ST_IsEmpty(ST_Union( \
geom, \
ST_GeomFromText('POLYGON((-179.9 -89.9, -179 -89.9, -179 -89, -179.9 -89, -179.9 -89.9))', 4326) \
))";
let mut group = c.benchmark_group("ST_Union disjoint bulk");
group.throughput(Throughput::Elements(N_POINTS as u64));
group.bench_function(BenchmarkId::new("sqlitegis", N_POINTS), |b| {
b.iter(|| unsafe { black_box(query_count(db_g, sql)) })
});
group.bench_function(BenchmarkId::new("spatialite", N_POINTS), |b| {
b.iter(|| unsafe { black_box(query_count(db_s, sql)) })
});
group.finish();
unsafe {
sqlite3_close(db_g);
sqlite3_close(db_s);
}
}
fn bench_sym_difference_disjoint(c: &mut Criterion) {
let db_g = unsafe { open_sqlitegis_db() };
let db_s = unsafe { open_spatialite_db() };
let sql = "SELECT COUNT(*) FROM regions \
WHERE NOT ST_IsEmpty(ST_SymDifference( \
geom, \
ST_GeomFromText('POLYGON((-179.9 -89.9, -179 -89.9, -179 -89, -179.9 -89, -179.9 -89.9))', 4326) \
))";
let mut group = c.benchmark_group("ST_SymDifference disjoint bulk");
group.throughput(Throughput::Elements(N_POINTS as u64));
group.bench_function(BenchmarkId::new("sqlitegis", N_POINTS), |b| {
b.iter(|| unsafe { black_box(query_count(db_g, sql)) })
});
group.bench_function(BenchmarkId::new("spatialite", N_POINTS), |b| {
b.iter(|| unsafe { black_box(query_count(db_s, sql)) })
});
group.finish();
unsafe {
sqlite3_close(db_g);
sqlite3_close(db_s);
}
}
fn bench_distance_spheroid(c: &mut Criterion) {
let db_g = unsafe { open_sqlitegis_db() };
let db_s = unsafe { open_spatialite_db() };
let sql_g =
"SELECT SUM(ST_DistanceSpheroid(geom, ST_GeomFromText('POINT(0 0)', 4326))) FROM places";
let sql_s = "SELECT SUM(ST_Distance(geom, ST_GeomFromText('POINT(0 0)', 4326), 1)) FROM places";
let mut group = c.benchmark_group("Geodesic distance spheroid bulk");
group.throughput(Throughput::Elements(N_POINTS as u64));
group.bench_function(BenchmarkId::new("sqlitegis", N_POINTS), |b| {
b.iter(|| unsafe { black_box(query_count(db_g, sql_g)) })
});
group.bench_function(BenchmarkId::new("spatialite", N_POINTS), |b| {
b.iter(|| unsafe { black_box(query_count(db_s, sql_s)) })
});
group.finish();
unsafe {
sqlite3_close(db_g);
sqlite3_close(db_s);
}
}
fn bench_bulk_dwithin_sphere_unindexed(c: &mut Criterion) {
let db_g = unsafe { open_sqlitegis_db() };
let db_s = unsafe { open_spatialite_db() };
let sql_g = "SELECT COUNT(*) FROM places \
WHERE ST_DWithinSphere(geom, ST_GeomFromText('POINT(0 0)', 4326), 500000.0)";
let sql_s = "SELECT COUNT(*) FROM places \
WHERE ST_Distance(geom, ST_GeomFromText('POINT(0 0)', 4326), 0) <= 500000.0";
let mut group = c.benchmark_group("Unindexed ST_DWithinSphere bulk");
group.throughput(Throughput::Elements(N_POINTS as u64));
group.bench_function(BenchmarkId::new("sqlitegis", N_POINTS), |b| {
b.iter(|| unsafe { black_box(query_count(db_g, sql_g)) })
});
group.bench_function(BenchmarkId::new("spatialite", N_POINTS), |b| {
b.iter(|| unsafe { black_box(query_count(db_s, sql_s)) })
});
group.finish();
unsafe {
sqlite3_close(db_g);
sqlite3_close(db_s);
}
}
fn bench_bulk_dwithin_spheroid_unindexed(c: &mut Criterion) {
let db_g = unsafe { open_sqlitegis_db() };
let db_s = unsafe { open_spatialite_db() };
let sql_g = "SELECT COUNT(*) FROM places \
WHERE ST_DWithinSpheroid(geom, ST_GeomFromText('POINT(0 0)', 4326), 500000.0)";
let sql_s = "SELECT COUNT(*) FROM places \
WHERE ST_Distance(geom, ST_GeomFromText('POINT(0 0)', 4326), 1) <= 500000.0";
let mut group = c.benchmark_group("Unindexed ST_DWithinSpheroid bulk");
group.throughput(Throughput::Elements(N_POINTS as u64));
group.bench_function(BenchmarkId::new("sqlitegis", N_POINTS), |b| {
b.iter(|| unsafe { black_box(query_count(db_g, sql_g)) })
});
group.bench_function(BenchmarkId::new("spatialite", N_POINTS), |b| {
b.iter(|| unsafe { black_box(query_count(db_s, sql_s)) })
});
group.finish();
unsafe {
sqlite3_close(db_g);
sqlite3_close(db_s);
}
}
fn bench_envelope_throughput(c: &mut Criterion) {
let db_g = unsafe { open_sqlitegis_db() };
let db_s = unsafe { open_spatialite_db() };
let sql = "SELECT SUM(ST_Area(ST_Envelope(geom))) FROM regions";
let mut group = c.benchmark_group("ST_Envelope scalar throughput");
group.throughput(Throughput::Elements(N_POINTS as u64));
group.bench_function(BenchmarkId::new("sqlitegis", N_POINTS), |b| {
b.iter(|| unsafe { black_box(query_count(db_g, sql)) })
});
group.bench_function(BenchmarkId::new("spatialite", N_POINTS), |b| {
b.iter(|| unsafe { black_box(query_count(db_s, sql)) })
});
group.finish();
unsafe {
sqlite3_close(db_g);
sqlite3_close(db_s);
}
}
fn bench_difference_overlapping(c: &mut Criterion) {
let db_g = unsafe { open_sqlitegis_db() };
let db_s = unsafe { open_spatialite_db() };
let sql = "SELECT COUNT(*) FROM regions \
WHERE NOT ST_IsEmpty(ST_Difference( \
geom, \
ST_GeomFromText('POLYGON((-180 -90,180 -90,180 90,-180 90,-180 -90))', 4326) \
))";
let mut group = c.benchmark_group("ST_Difference overlapping bulk");
group.throughput(Throughput::Elements(N_POINTS as u64));
group.bench_function(BenchmarkId::new("sqlitegis", N_POINTS), |b| {
b.iter(|| unsafe { black_box(query_count(db_g, sql)) })
});
group.bench_function(BenchmarkId::new("spatialite", N_POINTS), |b| {
b.iter(|| unsafe { black_box(query_count(db_s, sql)) })
});
group.finish();
unsafe {
sqlite3_close(db_g);
sqlite3_close(db_s);
}
}
fn bench_geomfromtext_throughput(c: &mut Criterion) {
let db_g = unsafe { open_sqlitegis_db() };
let db_s = unsafe { open_spatialite_db() };
let sql = "SELECT COUNT(ST_GeomFromText('POINT(1 2)', 4326)) FROM places";
let mut group = c.benchmark_group("ST_GeomFromText parse throughput");
group.throughput(Throughput::Elements(N_POINTS as u64));
group.bench_function(BenchmarkId::new("sqlitegis", N_POINTS), |b| {
b.iter(|| unsafe { black_box(query_count(db_g, sql)) })
});
group.bench_function(BenchmarkId::new("spatialite", N_POINTS), |b| {
b.iter(|| unsafe { black_box(query_count(db_s, sql)) })
});
group.finish();
unsafe {
sqlite3_close(db_g);
sqlite3_close(db_s);
}
}
fn bench_geomfromwkb_throughput(c: &mut Criterion) {
let db_g = unsafe { open_sqlitegis_db() };
let db_s = unsafe { open_spatialite_db() };
let sql = "SELECT COUNT(ST_GeomFromWKB(ST_AsBinary(ST_Point(1, 2)), 4326)) FROM places";
let mut group = c.benchmark_group("ST_GeomFromWKB parse throughput");
group.throughput(Throughput::Elements(N_POINTS as u64));
group.bench_function(BenchmarkId::new("sqlitegis", N_POINTS), |b| {
b.iter(|| unsafe { black_box(query_count(db_g, sql)) })
});
group.bench_function(BenchmarkId::new("spatialite", N_POINTS), |b| {
b.iter(|| unsafe { black_box(query_count(db_s, sql)) })
});
group.finish();
unsafe {
sqlite3_close(db_g);
sqlite3_close(db_s);
}
}
fn bench_asbinary_throughput(c: &mut Criterion) {
let db_g = unsafe { open_sqlitegis_db() };
let db_s = unsafe { open_spatialite_db() };
let sql = "SELECT SUM(LENGTH(ST_AsBinary(geom))) FROM places";
let mut group = c.benchmark_group("ST_AsBinary serialize throughput");
group.throughput(Throughput::Elements(N_POINTS as u64));
group.bench_function(BenchmarkId::new("sqlitegis", N_POINTS), |b| {
b.iter(|| unsafe { black_box(query_count(db_g, sql)) })
});
group.bench_function(BenchmarkId::new("spatialite", N_POINTS), |b| {
b.iter(|| unsafe { black_box(query_count(db_s, sql)) })
});
group.finish();
unsafe {
sqlite3_close(db_g);
sqlite3_close(db_s);
}
}
fn bench_asgeojson_throughput(c: &mut Criterion) {
let db_g = unsafe { open_sqlitegis_db() };
let db_s = unsafe { open_spatialite_db() };
let sql_g = "SELECT SUM(LENGTH(ST_AsGeoJSON(geom))) FROM places";
let sql_s = "SELECT SUM(LENGTH(AsGeoJSON(geom))) FROM places";
let mut group = c.benchmark_group("ST_AsGeoJSON serialize throughput");
group.throughput(Throughput::Elements(N_POINTS as u64));
group.bench_function(BenchmarkId::new("sqlitegis", N_POINTS), |b| {
b.iter(|| unsafe { black_box(query_count(db_g, sql_g)) })
});
group.bench_function(BenchmarkId::new("spatialite", N_POINTS), |b| {
b.iter(|| unsafe { black_box(query_count(db_s, sql_s)) })
});
group.finish();
unsafe {
sqlite3_close(db_g);
sqlite3_close(db_s);
}
}
fn bench_area_throughput(c: &mut Criterion) {
let db_g = unsafe { open_sqlitegis_db() };
let db_s = unsafe { open_spatialite_db() };
let sql = "SELECT SUM(ST_Area(geom)) FROM regions";
let mut group = c.benchmark_group("ST_Area sum");
group.throughput(Throughput::Elements(N_POINTS as u64));
group.bench_function(BenchmarkId::new("sqlitegis", N_POINTS), |b| {
b.iter(|| unsafe { black_box(query_count(db_g, sql)) })
});
group.bench_function(BenchmarkId::new("spatialite", N_POINTS), |b| {
b.iter(|| unsafe { black_box(query_count(db_s, sql)) })
});
group.finish();
unsafe {
sqlite3_close(db_g);
sqlite3_close(db_s);
}
}
fn bench_distance_planar(c: &mut Criterion) {
let db_g = unsafe { open_sqlitegis_db() };
let db_s = unsafe { open_spatialite_db() };
let sql = "SELECT SUM(ST_Distance(geom, ST_GeomFromText('POINT(0 0)', 4326))) FROM places";
let mut group = c.benchmark_group("ST_Distance planar bulk");
group.throughput(Throughput::Elements(N_POINTS as u64));
group.bench_function(BenchmarkId::new("sqlitegis", N_POINTS), |b| {
b.iter(|| unsafe { black_box(query_count(db_g, sql)) })
});
group.bench_function(BenchmarkId::new("spatialite", N_POINTS), |b| {
b.iter(|| unsafe { black_box(query_count(db_s, sql)) })
});
group.finish();
unsafe {
sqlite3_close(db_g);
sqlite3_close(db_s);
}
}
fn bench_perimeter_throughput(c: &mut Criterion) {
let db_g = unsafe { open_sqlitegis_db() };
let db_s = unsafe { open_spatialite_db() };
let sql = "SELECT SUM(ST_Perimeter(geom)) FROM regions";
let mut group = c.benchmark_group("ST_Perimeter sum");
group.throughput(Throughput::Elements(N_POINTS as u64));
group.bench_function(BenchmarkId::new("sqlitegis", N_POINTS), |b| {
b.iter(|| unsafe { black_box(query_count(db_g, sql)) })
});
group.bench_function(BenchmarkId::new("spatialite", N_POINTS), |b| {
b.iter(|| unsafe { black_box(query_count(db_s, sql)) })
});
group.finish();
unsafe {
sqlite3_close(db_g);
sqlite3_close(db_s);
}
}
fn bench_x_throughput(c: &mut Criterion) {
let db_g = unsafe { open_sqlitegis_db() };
let db_s = unsafe { open_spatialite_db() };
let sql = "SELECT SUM(ST_X(geom)) FROM places";
let mut group = c.benchmark_group("ST_X sum");
group.throughput(Throughput::Elements(N_POINTS as u64));
group.bench_function(BenchmarkId::new("sqlitegis", N_POINTS), |b| {
b.iter(|| unsafe { black_box(query_count(db_g, sql)) })
});
group.bench_function(BenchmarkId::new("spatialite", N_POINTS), |b| {
b.iter(|| unsafe { black_box(query_count(db_s, sql)) })
});
group.finish();
unsafe {
sqlite3_close(db_g);
sqlite3_close(db_s);
}
}
fn bench_y_throughput(c: &mut Criterion) {
let db_g = unsafe { open_sqlitegis_db() };
let db_s = unsafe { open_spatialite_db() };
let sql = "SELECT SUM(ST_Y(geom)) FROM places";
let mut group = c.benchmark_group("ST_Y sum");
group.throughput(Throughput::Elements(N_POINTS as u64));
group.bench_function(BenchmarkId::new("sqlitegis", N_POINTS), |b| {
b.iter(|| unsafe { black_box(query_count(db_g, sql)) })
});
group.bench_function(BenchmarkId::new("spatialite", N_POINTS), |b| {
b.iter(|| unsafe { black_box(query_count(db_s, sql)) })
});
group.finish();
unsafe {
sqlite3_close(db_g);
sqlite3_close(db_s);
}
}
criterion_group!(
benches,
bench_bulk_intersects_unindexed,
bench_indexed_intersects,
bench_distance_sphere,
bench_astext_throughput,
bench_buffer_intersection,
bench_bulk_contains_unindexed,
bench_indexed_contains,
bench_buffer_throughput,
bench_centroid_throughput,
bench_difference_disjoint,
bench_bulk_covers_unindexed,
bench_bulk_touches_unindexed,
bench_bulk_overlaps_unindexed,
bench_bulk_equals_unindexed,
bench_bulk_dwithin_unindexed,
bench_union_disjoint,
bench_sym_difference_disjoint,
bench_distance_spheroid,
bench_bulk_dwithin_sphere_unindexed,
bench_bulk_dwithin_spheroid_unindexed,
bench_envelope_throughput,
bench_difference_overlapping,
bench_geomfromtext_throughput,
bench_geomfromwkb_throughput,
bench_asbinary_throughput,
bench_asgeojson_throughput,
bench_area_throughput,
bench_distance_planar,
bench_perimeter_throughput,
bench_x_throughput,
bench_y_throughput,
);
criterion_main!(benches);