Skip to content

perf: render shapes via PathCollection of pre-built Paths (avoid per-shape Patch construction) #733

Description

@timtreis

Reframed after profiling (the original to_rgba framing was wrong — it's end-to-end noise).

Root cause

render_shapes (matplotlib) is dominated by per-shape Patch construction, identical across no-color / categorical / continuous color (cProfile, 200k circles, ~23 s):

  • _build_shape_patches (_geometry.py): ~13 s — one mpatches.Circle/Polygon per shape; each Patch.__init__ resolves default colors via to_rgba (~4×/patch) + recomputes a per-patch transform.
  • PatchCollection.set_paths: ~6 s — converting all N patches to Paths.

Fix: PathCollection of pre-built Paths

Build Path objects (not Patch objects) and return a matplotlib.collections.PathCollection. These are exactly the Paths PatchCollection.set_paths produces internally, so it's byte-identical (same backend), preserves holes (compound paths) and the existing per-path affine step, keeps all color/alpha/outline logic, is one code path, and needs no signature change.

Measured (200k circles, build+draw): ~2.6× faster, ~5.8× less memory (11.7 s/149 MB vs 30.1 s/867 MB), color-mode-independent.

Rejected: EllipseCollection/PolyCollection — incompatible with the coordinate-system affine applied per-path (EllipseCollection has one shared read-only path), PolyCollection can't represent holes, and EllipseCollection tessellates circles coarsely. Not byte-identical.

Plan: plans/issue-733.md. Datashader + as_points paths already optimized — untouched.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions