Commit Graph
8 Commits
Author SHA1 Message Date
Claude 9ff3f92e6e Spread shared node marking across all CPUs regardless of readers
Marking used one thread per reader, so input that was read serially,
which all goes to the first reader, was marked by a single thread.
Instead, use the readers' indexes to divide all the geometry into
ranges of about the same size at feature boundaries, and have each
thread take the next unmarked range until there are none left.

With 4 CPUs and input read by one reader, this makes marking about
3x faster.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P2nBqZisxNQfmEmon3vE9v
2026-09-23 20:02:49 +00:00
Claude 58ad6e3008 Use the faster bit interleave for encode_quadkey itself
encode_vertex() computed exactly the same thing as encode_quadkey(),
so there is no reason to have both. Give encode_quadkey() the branch-free
implementation, which also speeds up the default encode_index, and have
the shared node code call it directly. The unit test now compares it
against the old bit-at-a-time loop and checks that decode_quadkey()
reverses it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P2nBqZisxNQfmEmon3vE9v
2026-09-23 20:02:49 +00:00
Claude e1795ebe32 Make the remaining shared node lookups faster
Encode the shared nodes as quadkeys, as the comment on struct node
already said they were, with a branch-free bit interleave, so that
vertices that are near each other are near each other in the sorted list
too. Search the list with an inlined std::lower_bound instead of bsearch.

Size the Bloom filter by the number of nodes, at about 16 bits each and
at most 32MB, instead of always using 34MB, so that it can usually stay
in the cache, and set three bits for each node, chosen by a mixing hash,
within a single 64-bit word, so that each check still touches only one
cache line but has far fewer false positives than a single bit.

In the pass that marks the vertices with whether they are shared nodes,
this is about 1.7x faster with 70 thousand nodes and 1.9x faster with
3 million.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P2nBqZisxNQfmEmon3vE9v
2026-09-23 19:46:09 +00:00
Claude 175036930d Test --no-simplification-of-shared-nodes with --clip-bounding-box
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P2nBqZisxNQfmEmon3vE9v
2026-09-23 18:28:32 +00:00
Claude f03cc8ce0e Treat points created by clipping and tiny polygon placeholders as not shared
Points that clipping creates along a feature's edges, other than at the
existing vertices, and the vertices of tiny polygon placeholders, are
not vertices of the original geometry, so they are now marked as not
being shared nodes instead of being looked up in the global list of
shared nodes when they are simplified. This could only change the output
where a vertex of some other feature happens to fall exactly on one of
these new points, and in practice none of them ever turned out to be
shared nodes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P2nBqZisxNQfmEmon3vE9v
2026-09-23 18:28:04 +00:00
Claude 1dee1890e7 Mask the node state out of the operation when marking shared nodes
Geometry that was clipped to --clip-bounding-box while it was being
serialized can already have node states in its operation bytes, so the
unmasked comparison against VT_MOVETO and VT_LINETO failed to recognize
those vertices and lost track of where the following vertices began.
Also stop with an error instead of continuing if the operation is not
one that can appear in serialized geometry.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P2nBqZisxNQfmEmon3vE9v
2026-09-23 18:28:04 +00:00
Claude ee5debc4f2 Don't look up shared nodes that are already necessary
Vertices that are already going to be kept, because they begin a ring
or are on the tile boundary, can't be affected by whether they are also
global shared nodes, so skip looking them up. These are most of the
vertices that clipping creates, which don't know their node state.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P2nBqZisxNQfmEmon3vE9v
2026-09-23 18:09:52 +00:00
Claude d3a98170bf Look up shared nodes once per vertex instead of once per tile
With --no-simplification-of-shared-nodes, simplify_lines() used to offset
every vertex of every feature to world coordinates and check it against
the Bloom filter and the global sorted list of shared nodes, in every
tile at every zoom level.

Whether a vertex is a shared node depends only on its world coordinates,
so now it is found once, after the list of shared nodes has been made
and before the geometry is sorted, by a parallel pass over each reader's
geometry that marks each vertex in place, in the upper bits of its
serialized operation byte. Decoding puts that state into a new field of
draw (which still fits in 16 bytes), and it is carried through clipping,
the copies across the antimeridian at z0, and the geometry written for
the next zoom level. Polygon cleaning of coalesced features restores the
state of any output vertex with the same coordinates as an input vertex.

Vertices whose state is still unknown, because they were created by
clipping or polygon cleaning or came back from a prefilter, are still
looked up in the global list when they are simplified, so the output is
unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P2nBqZisxNQfmEmon3vE9v
2026-09-23 17:39:48 +00:00