-
Notifications
You must be signed in to change notification settings - Fork 340
S2Cell::GetDistance: Optimize same-face case #479
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Use the uv coordinates to prune the vertex-edge distance computations needed. When cell A is above B, we don't need to compare the top vertices/edge of A with the bottom vertices/edge of B. Note, however, that when A is both above and left of B, we cannot just compute the lower-right/upper-left vertex distance due to the projection. It is possible that more comparisons can be omitted; I'm not sure. Error is within 1e-15 radians of previous results. GetDistance shows a 2x speedup for same-face cells.
|
@ericveach Could you take a look a this and see if it is close to what you had in mind with your TODO here? Lines 512 to 514 in ed62eee
|
|
Hi Jesse,
Could you try this instead? (Again, no guarantees this will even compile,
much less work.)
I'm pretty sure (i.e. I think I've proved) that it's sufficient to only
check the axis (u or v) where the two cells have the greatest u or v
separation.
In any case, testing is the easiest way to check whether I'm wrong.
Eric
if (face_ == target.face_) {
// Find the index "ai" of the edge of A that is furthest away from the
// opposite edge of B in (u,v)-space.
int ai = -1;
double max_dist = 0;
auto checkEdge = [&ai, &max_dist](double dist, int a_edge) {
if (dist > max_dist) {
ai = a_edge;
max_dist = dist;
}
};
checkEdge(uv_[0][0] - target.uv_[0][1], kLeftEdge);
checkEdge(target.uv_[0][0] - uv_[0][1], kRightEdge);
checkEdge(uv_[1][0] - target.uv_[1][1], kTopEdge);
checkEdge(target.uv_[1][0] - uv_[1][1], kBottomEdge);
if (ai < 0) {
// A and B intersect (including edge and vertex intersections).
return S1ChordAngle::Zero();
}
// Otherwise the minimum distance always occurs between an endpoint of
the
// edge "ai" and the opposite edge (ai ^ 2) of B, or symmetrically, an
// endpoint of the opposite edge of B and the edge "ai".
int bi = ai ^ 2;
S2::UpdateMinDistance(va[ai], vb[bi], vb[bi + 1], &min_dist);
S2::UpdateMinDistance(va[ai + 1], vb[bi], vb[bi + 1], &min_dist);
S2::UpdateMinDistance(vb[bi], va[ai], va[ai + 1], &min_dist);
S2::UpdateMinDistance(vb[bi + 1], va[ai], va[ai + 1], &min_dist);
return min_dist;
}
…On Mon, Nov 24, 2025 at 2:42 AM Jesse Rosenstock ***@***.***> wrote:
*jmr* left a comment (google/s2geometry#479)
<#479 (comment)>
@ericveach <https://github.com/ericveach> Could you take a look a this
and see if it is close to what you had in mind with your TODO here?
https://github.com/google/s2geometry/blob/ed62eeeaa92f19c70aeaa91e8acbd3b5c1171a57/src/s2/s2cell.cc#L512-L514
—
Reply to this email directly, view it on GitHub
<#479 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AICG3CQDPV7UBSGNAL3MCHD36LOILAVCNFSM6AAAAACNAK3BLSVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZTKNZQGA3TEMJYHA>
.
You are receiving this because you were mentioned.Message ID:
***@***.***>
|
|
Somehow the formatting seems to get screwed up when I paste from a terminal window. Here's another attempt. |
|
Thanks, Eric! #479 (comment) works for me after I swapped |
|
I have incorporated your suggestion, with a separate |
|
Although I can't build the actual library right now I did try pasting some version of the code I suggested into compiler explorer, and when built under Clang the FindFurthestEdge portion is branchless. Given this and the fact that only 4 edges are tested, how much faster is it now? Unfortunately GCC doesn't seem willing to generate branchless code, but there's not much we can do about that. |
Before it was 80% This gives an overall 3x speedup. We can take the win and continue to optimize. The largest chunk of the remaining time is in vector
I can get gcc to generate branchless code by giving it a branch probability somewhere between 0.21 and 0.33. https://godbolt.org/z/rhqGcrqE9 It would be interesting to see the actual branch probability and what FDO did. |
|
It's also worth mentioning that only 1/5 as many instructions were executed and IPC decreased from 3.2 to 1.8 (less unnecessary extra work to be done in parallel). Working on releasing benchmarks now. |
|
Thanks for the comprehensive analysis! It looks like the performance analysis tools available have improved a lot in the past few years. Just to clarify, are the results you mentioned for random cells, or for cells on the same face, or for cells that are small relative to their separation distance? I suspect that the last case may be the most important in practice, e.g. where the separation distance may be up to a few hundred or thousands of km but the cell sizes are, say, only 1% or 10% of that distance. This is the situation you would often have when measuring distances between coverings of real-world geometry, for example. |
|
Maybe your existing change is big enough, but if you wanted to try adding the vertex-only case, here is a stab at it: Again, no guarantees that this will work or even compile. But at least in the case of cells that are small relative to their separation distance, and where the separation distance is small relative to the Earth's radius, it should yield a substantial speedup. |
Definitely. I used:
Random same-face cells. // Copyright 2025 Google LLC.
// SPDX-License-Identifier: Apache-2.0
static void BM_GetDistanceToCellSameFace(benchmark::State& state) {
const string seed_str = StrCat("GET_DISTANCE_TO_CELL_SAME_FACE",
absl::GetFlag(FLAGS_s2_random_seed));
std::seed_seq seed(seed_str.begin(), seed_str.end());
std::mt19937_64 bitgen(seed);
std::vector<S2Cell> cells;
cells.reserve(kBatchSize);
for (int i = 0; i < kBatchSize; ++i) {
// Make a cell id and move it to face 0.
S2CellId cellid = s2random::CellId(bitgen);
cells.emplace_back(
S2CellId::FromFacePosLevel(0, cellid.pos(), cellid.level()));
}
int i = 0;
while (state.KeepRunningBatch(kBatchSize)) {
const S2Cell& cell1 = (cells)[i];
for (const S2Cell& cell2 : cells) {
S1ChordAngle distance = cell1.GetDistance(cell2);
benchmark::DoNotOptimize(distance);
}
if (++i == kBatchSize) i = 0;
}
}
BENCHMARK(BM_GetDistanceToCellSameFace);A more realistic version of this could be done. There are also larger-scale benchmarks. Re #479 (comment). Thanks for that. I will try it, but definitely separately. |
|
When I naively try #479 (comment), it's about 5% faster on |
|
Thanks for the trying that and also the profiling info. Your benchmark looks pretty good; the only downside is that I think it probably significantly overweights large cells, since s2random::S2CellId() chooses the cell level randomly between 0 and 30. So for example, ~10% of the cells will be 2500km across or larger, and ~19% of random pairs will involve a cell at least this big. This means the test is significantly weighted towards pairs that overlap or that are not well separated relative to their size. If you wanted to test the well-separated case specifically, cell levels [10..30] might be an appropriate choice. |
* Add S2Cell::IsDistanceLess implementation along the lines of GetDistance * Rename "HighErrorExample" test to "HighDifferenceExample" * Use UpdateMinInteriorDistance when endpoints have already been checked * Reword FindFurthestEdge comment * Rename GetDistanceToCellBruteForce args
ericveach
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good!
|
No, I was just trying to clarify what this test is actually testing. It's
fine as is.
…On Sun, Dec 7, 2025 at 6:39 AM Jesse Rosenstock ***@***.***> wrote:
***@***.**** commented on this pull request.
------------------------------
In src/s2/s2cell_test.cc
<#479 (comment)>:
> + SCOPED_TRACE(StrCat("Iteration ", iter));
+ S2Cell cell1(s2random::CellId(bitgen));
+ S2Cell cell2(s2random::CellId(bitgen));
+ S1ChordAngle expected = GetDistanceToCellBruteForce(cell1, cell2);
+ S1ChordAngle actual = cell1.GetDistance(cell2);
+ EXPECT_NEAR(expected.radians(), actual.radians(), 1e-15)
+ << "cell1: " << cell1.id() << " cell2: " << cell2.id()
+ << " cell1.uv: " << cell1.GetBoundUV()
+ << " cell2.uv: " << cell2.GetBoundUV();
+ }
+}
+
+TEST(S2Cell, GetDistanceToCellHighErrorExample) {
+ // This is a test case extracted from `GetDistanceToCell`; it achieved
+ // the maximum error over 100M iterations, so is useful to understand
+ // the errors and as a shortcut for testing.
Ok. I thought you wanted me to add a test using
GetUpdateMinDistanceMaxError.
—
Reply to this email directly, view it on GitHub
<#479 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AICG3CVXWK5JQQGVNS77QNL4APYVHAVCNFSM6AAAAACNAK3BLSVHI2DSMVQWIX3LMV43YUDVNRWFEZLROVSXG5CSMV3GSZLXHMZTKNBZGE4DSNBVGU>
.
You are receiving this because you were mentioned.Message ID:
***@***.***>
|
|
Eric, thank you for your time and careful attention. |
Use the uv coordinates to prune the vertex-edge distance computations needed.
When cell A is above B, we don't need to compare the top vertices/edge of A with the bottom vertices/edge of B.
Note, however, that when A is both above and left of B, we cannot just compute the lower-right/upper-left vertex distance due to the projection.
It is possible that more comparisons can be omitted; I'm not sure.
Error is within 1e-15 radians of previous results.
GetDistance shows a 2x speedup for same-face cells.