Skip to content

Commit

Permalink
Geospatial basic queries benchmarks (#6621)
Browse files Browse the repository at this point in the history
* Add basic benchmarks for Geospatial type and queries

* Less copying in GeoWithinCompare

* Bring back caching of s2 region into Geospatial

* remove transaction overhead from measurements

* a couple small optimizations

* formatting

* simplify geospatial query evaluations

* changelog

---------

Co-authored-by: James Stone <[email protected]>
  • Loading branch information
kiburtse and ironage authored May 19, 2023
1 parent 4536bc2 commit 2d8fb23
Show file tree
Hide file tree
Showing 6 changed files with 285 additions and 63 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@

### Internals
* Upgraded to Catch from v3.0.1 to v3.3.2. ([#6623](https://github.com/realm/realm-core/issues/6623))
* Added some geospatial benchmarks. ([#6622](https://github.com/realm/realm-core/issues/6622))

----------------------------------------------

Expand Down
59 changes: 38 additions & 21 deletions src/realm/geospatial.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,11 @@

namespace {

static bool type_is_valid(std::string str_type)
static bool type_is_valid(realm::StringData str_type)
{
std::transform(str_type.begin(), str_type.end(), str_type.begin(), realm::toLowerAscii);
return str_type == "point";
return str_type.size() == 5 && (str_type[0] == 'P' || str_type[0] == 'p') &&
(str_type[1] == 'o' || str_type[1] == 'O') && (str_type[2] == 'i' || str_type[2] == 'I') &&
(str_type[3] == 'n' || str_type[3] == 'N') && (str_type[4] == 't' || str_type[4] == 'T');
}

} // anonymous namespace
Expand Down Expand Up @@ -108,7 +109,7 @@ bool Geospatial::is_geospatial(const TableRef table, ColKey link_col)
return true;
}

Geospatial Geospatial::from_obj(const Obj& obj, ColKey type_col, ColKey coords_col)
std::optional<GeoPoint> Geospatial::point_from_obj(const Obj& obj, ColKey type_col, ColKey coords_col)
{
if (!type_col) {
type_col = obj.get_table()->get_column_key(StringData(c_geo_point_type_col_name));
Expand All @@ -125,16 +126,16 @@ Geospatial Geospatial::from_obj(const Obj& obj, ColKey type_col, ColKey coords_c
REALM_ASSERT(coords_col.is_list());
}

String geo_type = obj.get<String>(type_col);
if (!type_is_valid(geo_type)) {
if (!type_is_valid(obj.get<StringData>(type_col))) {
throw IllegalOperation("The only Geospatial type currently supported is 'point'");
}

Lst<double> coords = obj.get_list<double>(coords_col);
if (coords.size() < 2) {
return Geospatial(); // invalid
const size_t coord_size = coords.size();
if (coord_size < 2) {
return {}; // invalid
}
if (coords.size() > 2) {
if (coord_size > 2) {
return GeoPoint{coords[0], coords[1], coords[2]};
}
return GeoPoint{coords[0], coords[1]};
Expand Down Expand Up @@ -188,28 +189,29 @@ void Geospatial::assign_to(Obj& link) const
auto&& point = get<GeoPoint>();
link.set(type_col, get_type_string());
Lst<double> coords = link.get_list<double>(coords_col);
if (coords.size() >= 1) {
size_t existing_size = coords.size();
std::optional<double> altitude = point.get_altitude();
if (existing_size >= 1) {
coords.set(0, point.longitude);
}
else {
coords.add(point.longitude);
}
if (coords.size() >= 2) {
if (existing_size >= 2) {
coords.set(1, point.latitude);
}
else {
coords.add(point.latitude);
}
std::optional<double> altitude = point.get_altitude();
if (altitude) {
if (coords.size() >= 3) {
if (existing_size >= 3) {
coords.set(2, *altitude);
}
else {
coords.add(*altitude);
}
}
else if (coords.size() >= 3) {
else if (existing_size >= 3) {
coords.remove(2, coords.size());
}
}
Expand All @@ -224,11 +226,26 @@ static std::string point_str(const GeoPoint& point)

Status Geospatial::is_valid() const noexcept
{
if (get_type() == Type::Polygon) {
GeoRegion region(*this);
return region.get_conversion_status();
switch (get_type()) {
case Type::Polygon:
case Type::Box:
case Type::Circle:
return get_region().get_conversion_status();
default:
return Status::OK();
}
return Status::OK();
}

bool Geospatial::contains(const GeoPoint& point) const noexcept
{
return get_region().contains(point);
}

GeoRegion& Geospatial::get_region() const
{
if (!m_region)
m_region = std::make_unique<GeoRegion>(*this);
return *m_region.get();
}

std::string Geospatial::to_string() const
Expand Down Expand Up @@ -497,12 +514,12 @@ GeoRegion::GeoRegion(const Geospatial& geo)

GeoRegion::~GeoRegion() = default;

bool GeoRegion::contains(const GeoPoint& geo_point) const noexcept
bool GeoRegion::contains(const std::optional<GeoPoint>& geo_point) const noexcept
{
if (!m_status.is_ok()) {
if (!m_status.is_ok() || !geo_point) {
return false;
}
auto point = S2LatLng::FromDegrees(geo_point.latitude, geo_point.longitude).ToPoint();
auto point = S2LatLng::FromDegrees(geo_point->latitude, geo_point->longitude).ToPoint();
return m_region->VirtualContainsPoint(point);
}

Expand Down
46 changes: 30 additions & 16 deletions src/realm/geospatial.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ namespace realm {

class Obj;
class TableRef;
class Geospatial;

struct GeoPoint {
GeoPoint() = delete;
Expand Down Expand Up @@ -168,6 +169,19 @@ struct GeoCircle {
}
};

class GeoRegion {
public:
GeoRegion(const Geospatial& geo);
~GeoRegion();

bool contains(const std::optional<GeoPoint>& point) const noexcept;
Status get_conversion_status() const noexcept;

private:
std::unique_ptr<S2Region> m_region;
Status m_status;
};

class Geospatial {
public:
enum class Type : uint8_t { Invalid, Point, Box, Polygon, Circle };
Expand All @@ -193,13 +207,22 @@ class Geospatial {
{
}

Geospatial(const Geospatial&) = default;
Geospatial& operator=(const Geospatial&) = default;
Geospatial(const Geospatial& other)
: m_value(other.m_value)
{
}
Geospatial& operator=(const Geospatial& other)
{
if (this != &other) {
m_value = other.m_value;
}
return *this;
}

Geospatial(Geospatial&& other) = default;
Geospatial& operator=(Geospatial&&) = default;

static Geospatial from_obj(const Obj& obj, ColKey type_col = {}, ColKey coords_col = {});
static std::optional<GeoPoint> point_from_obj(const Obj& obj, ColKey type_col = {}, ColKey coords_col = {});
static Geospatial from_link(const Obj& obj);
static bool is_geospatial(const TableRef table, ColKey link_col);
void assign_to(Obj& link) const;
Expand All @@ -212,7 +235,8 @@ class Geospatial {

Status is_valid() const noexcept;

bool is_within(const Geospatial& bounds) const noexcept;
bool contains(const GeoPoint& point) const noexcept;

std::string to_string() const;

bool operator==(const Geospatial& other) const
Expand All @@ -236,19 +260,9 @@ class Geospatial {
mpark::variant<mpark::monostate, GeoPoint, GeoBox, GeoPolygon, GeoCircle> m_value;

friend class GeoRegion;
};

class GeoRegion {
public:
GeoRegion(const Geospatial& geo);
~GeoRegion();

bool contains(const GeoPoint& point) const noexcept;
Status get_conversion_status() const noexcept;

private:
std::unique_ptr<S2Region> m_region;
Status m_status;
mutable std::unique_ptr<GeoRegion> m_region;
GeoRegion& get_region() const;
};

template <>
Expand Down
34 changes: 9 additions & 25 deletions src/realm/query_expression.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -2354,9 +2354,9 @@ class BacklinkCount : public Subexpr2<Int> {
#if REALM_ENABLE_GEOSPATIAL
class GeoWithinCompare : public Expression {
public:
GeoWithinCompare(const LinkMap& lm, Geospatial bounds, util::Optional<ExpressionComparisonType> comp_type)
GeoWithinCompare(const LinkMap& lm, Geospatial&& bounds, util::Optional<ExpressionComparisonType> comp_type)
: m_link_map(lm)
, m_bounds(bounds)
, m_bounds(std::move(bounds))
, m_region(m_bounds)
, m_comp_type(comp_type)
{
Expand Down Expand Up @@ -2418,50 +2418,34 @@ class GeoWithinCompare : public Expression {

while (start < end) {
bool found = false;
// TODO: the map_links short circuit is not working, it is being fixed in a separate PR
// and when that lands the logic below can be simplified by removing the following counters
size_t num_matches = 0;
size_t num_misses = 0;
switch (m_comp_type.value_or(ExpressionComparisonType::Any)) {
case ExpressionComparisonType::Any: {
m_link_map.map_links(start, [&](ObjKey key) {
found = m_region.contains(
Geospatial::from_obj(table->get_object(key), m_type_col, m_coords_col).get<GeoPoint>());
if (found)
num_matches++;
else
num_misses++;
Geospatial::point_from_obj(table->get_object(key), m_type_col, m_coords_col));
return !found; // keep searching if not found, stop searching on first match
});
if (num_matches > 0)
if (found)
return start;
break;
}
case ExpressionComparisonType::All: {
m_link_map.map_links(start, [&](ObjKey key) {
found = m_region.contains(
Geospatial::from_obj(table->get_object(key), m_type_col, m_coords_col).get<GeoPoint>());
if (found)
num_matches++;
else
num_misses++;
Geospatial::point_from_obj(table->get_object(key), m_type_col, m_coords_col));
return found; // keep searching until one the first non-match
});
if (num_matches > 0 && num_misses == 0) // all matched
if (found) // all matched
return start;
break;
}
case ExpressionComparisonType::None: {
m_link_map.map_links(start, [&](ObjKey key) {
found = m_region.contains(
Geospatial::from_obj(table->get_object(key), m_type_col, m_coords_col).get<GeoPoint>());
if (found)
num_matches++;
else
num_misses++;
Geospatial::point_from_obj(table->get_object(key), m_type_col, m_coords_col));
return !found; // keep searching until the first match
});
if (num_matches == 0) // none matched
if (!found) // none matched
return start;
break;
}
Expand Down Expand Up @@ -2716,7 +2700,7 @@ class Columns<Link> : public Subexpr2<Link> {
#if REALM_ENABLE_GEOSPATIAL
Query geo_within(Geospatial bounds) const
{
return make_expression<GeoWithinCompare>(m_link_map, bounds, m_comparison_type);
return make_expression<GeoWithinCompare>(m_link_map, std::move(bounds), m_comparison_type);
}
#endif

Expand Down
2 changes: 1 addition & 1 deletion test/benchmark-common-tasks/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
add_executable(realm-benchmark-common-tasks main.cpp)
target_link_libraries(realm-benchmark-common-tasks TestUtil)
target_link_libraries(realm-benchmark-common-tasks TestUtil QueryParser)
add_dependencies(benchmarks realm-benchmark-common-tasks)

Loading

0 comments on commit 2d8fb23

Please sign in to comment.