Skip to content

Scalarized gca_gca_intersection - #1688

Draft
cmdupuis3 wants to merge 6 commits into
mainfrom
cmd/gca_no_allocation
Draft

Scalarized gca_gca_intersection#1688
cmdupuis3 wants to merge 6 commits into
mainfrom
cmd/gca_no_allocation

Conversation

@cmdupuis3

@cmdupuis3 cmdupuis3 commented Aug 19, 2026

Copy link
Copy Markdown
Collaborator

Tangent issue to #1623, also related to #1648

Overview

The existing gca_gca_intersection uses four np.ndarray objects of size 3. The overhead incurred by using these python objects is njit compiled loops is significant, and we get about a 2x speedup in there by scalarizing gca_gca_intersection, _try_gca_gca_intersection, and _accux_gca, and using _on_minor_arc_xyz instead of on_minor_arc.

PR Checklist

General

  • An issue is created and linked
  • Added appropriate labels (if your uxarray repo permissions allow it)
  • Filled out Overview and Expected Usage (if applicable) sections

Testing & Benchmarking

  • Adequate tests are created if there is new functionality
  • Tests are not too basic (such as simply calling a function and nothing else)
  • Tests cover all major paths in your new functions
  • If this PR could affect performance, ran ASV benchmarks and confirmed they show expected behavior (add a new benchmark if necessary)

Documentation

  • Docstrings have been added to all new functions
  • Docstrings have been updated with any function changes
  • User (public) functions have been added to docs/api.rst
  • Internal (private) function names start with an underscore (_)

AI Disclosure

AI Usage: Claude Opus 5 / Sonnet 5

  • I take responsibility for all AI-generated content in my PR.
  • I have tested all AI-generated content in my PR.

Add scalar-argument L1/L2 siblings (_accux_gca_scalar,
_try_gca_gca_intersection_scalar) and rewire gca_gca_intersection to them,
cutting 4 heap allocations per edge to 1. Bit-identical output; ~2.7x on
the function in isolation, ~1.24x on serial face bounds.
gca_gca_intersection no longer calls the array-form _accux_gca /
_try_gca_gca_intersection, so benchmarking them no longer reflects the
dispatcher's actual cost. Point at the scalar siblings instead, skipping
gracefully (via skip_benchmark_if) on commits that predate them.
@cmdupuis3 cmdupuis3 added scalability Related to scalability & performance efforts run-benchmark Run ASV benchmark workflow labels Aug 19, 2026
@cmdupuis3

Copy link
Copy Markdown
Collaborator Author

pre-commit.ci autofix

@cmdupuis3 cmdupuis3 linked an issue Aug 19, 2026 that may be closed by this pull request
@cmdupuis3 cmdupuis3 self-assigned this Aug 19, 2026
@cmdupuis3

Copy link
Copy Markdown
Collaborator Author

pre-commit.ci autofix

@github-actions

github-actions Bot commented Aug 19, 2026

Copy link
Copy Markdown

ASV Benchmarking

Benchmark Comparison Results

Benchmarks that have improved:

Change Before [9d5bcdb] After [e0bfadc] Ratio Benchmark (Parameter)
* failed 3.35±0.03μs n/a geometry_kernels.GCAGCAIntersection.time_accux_gca_kernel
- 1.26±0.03μs 1.13±0.02μs 0.90 geometry_kernels.GCAGCAIntersection.time_gca_gca_intersection
* failed 3.46±0.05μs n/a geometry_kernels.GCAGCAIntersection.time_try_gca_gca_intersection
- 216±10μs 195±5μs 0.91 mpas_ocean.HoleEdgeIndices.time_construct_hole_edge_indices('480km')

Benchmarks that have stayed the same:

Change Before [9d5bcdb] After [e0bfadc] Ratio Benchmark (Parameter)
223±2ms 261±40ms ~1.17 bench_connectivity.Connectivity.time_edge_face('120km')
13.2±0.1ms 12.7±0.09ms 0.96 bench_connectivity.Connectivity.time_edge_face('480km')
246±30ms 219±4ms ~0.89 bench_connectivity.Connectivity.time_edge_node('120km')
12.2±0.1ms 12.2±0.4ms 1.00 bench_connectivity.Connectivity.time_edge_node('480km')
221±1ms 215±2ms 0.97 bench_connectivity.Connectivity.time_face_edge('120km')
12.2±0.2ms 13.2±0.5ms 1.08 bench_connectivity.Connectivity.time_face_edge('480km')
789±9ms 788±4ms 1.00 bench_connectivity.Connectivity.time_face_face('120km')
47.8±0.2ms 48.3±0.8ms 1.01 bench_connectivity.Connectivity.time_face_face('480km')
62.1±2μs 67.7±5μs 1.09 bench_connectivity.Connectivity.time_face_node('120km')
60.3±2μs 57.8±2μs 0.96 bench_connectivity.Connectivity.time_face_node('480km')
425±6μs 426±9μs 1.00 bench_connectivity.Connectivity.time_n_nodes_per_face('120km')
306±3μs 299±7μs 0.98 bench_connectivity.Connectivity.time_n_nodes_per_face('480km')
223±3ms 218±1ms 0.98 bench_connectivity.Connectivity.time_node_edge('120km')
12.4±0.09ms 12.3±0.2ms 0.99 bench_connectivity.Connectivity.time_node_edge('480km')
86.8±2ms 85.1±8ms 0.98 bench_connectivity.Connectivity.time_node_face('120km')
4.70±0.06ms 4.76±0.1ms 1.01 bench_connectivity.Connectivity.time_node_face('480km')
7.28±0.2ms 6.39±0.1ms ~0.88 face_bounds.FaceBounds.time_face_bounds(PosixPath('/home/runner/work/uxarray/uxarray/test/meshfiles/mpas/QU/oQU480.231010.nc'))
2.50±0.1ms 2.16±0.1ms ~0.87 face_bounds.FaceBounds.time_face_bounds(PosixPath('/home/runner/work/uxarray/uxarray/test/meshfiles/scrip/outCSne8/outCSne8.nc'))
10.4±10s 8.19±10ms ~0.00 face_bounds.FaceBounds.time_face_bounds(PosixPath('/home/runner/work/uxarray/uxarray/test/meshfiles/ugrid/geoflow-small/grid.nc'))
1.85±0.03ms 1.87±0.02ms 1.01 face_bounds.FaceBounds.time_face_bounds(PosixPath('/home/runner/work/uxarray/uxarray/test/meshfiles/ugrid/quad-hexagon/grid.nc'))
57.3k 57.3k 1.00 face_bounds.FaceBounds.track_nbytes_face_bounds(PosixPath('/home/runner/work/uxarray/uxarray/test/meshfiles/mpas/QU/oQU480.231010.nc'))
12.3k 12.3k 1.00 face_bounds.FaceBounds.track_nbytes_face_bounds(PosixPath('/home/runner/work/uxarray/uxarray/test/meshfiles/scrip/outCSne8/outCSne8.nc'))
123k 123k 1.00 face_bounds.FaceBounds.track_nbytes_face_bounds(PosixPath('/home/runner/work/uxarray/uxarray/test/meshfiles/ugrid/geoflow-small/grid.nc'))
128 128 1.00 face_bounds.FaceBounds.track_nbytes_face_bounds(PosixPath('/home/runner/work/uxarray/uxarray/test/meshfiles/ugrid/quad-hexagon/grid.nc'))
1.27M 1.27M 1.00 face_bounds.FaceBounds.track_nbytes_grid_with_bounds(PosixPath('/home/runner/work/uxarray/uxarray/test/meshfiles/mpas/QU/oQU480.231010.nc'))
50.1k 50.1k 1.00 face_bounds.FaceBounds.track_nbytes_grid_with_bounds(PosixPath('/home/runner/work/uxarray/uxarray/test/meshfiles/scrip/outCSne8/outCSne8.nc'))
1.48M 1.48M 1.00 face_bounds.FaceBounds.track_nbytes_grid_with_bounds(PosixPath('/home/runner/work/uxarray/uxarray/test/meshfiles/ugrid/geoflow-small/grid.nc'))
712 712 1.00 face_bounds.FaceBounds.track_nbytes_grid_with_bounds(PosixPath('/home/runner/work/uxarray/uxarray/test/meshfiles/ugrid/quad-hexagon/grid.nc'))
2.03M 2.02M 0.99 face_bounds.FaceBounds.track_peakmem_face_bounds(PosixPath('/home/runner/work/uxarray/uxarray/test/meshfiles/mpas/QU/oQU480.231010.nc'))
2.03M 2.03M 1.00 face_bounds.FaceBounds.track_peakmem_face_bounds(PosixPath('/home/runner/work/uxarray/uxarray/test/meshfiles/scrip/outCSne8/outCSne8.nc'))
2.2M 2.19M 0.99 face_bounds.FaceBounds.track_peakmem_face_bounds(PosixPath('/home/runner/work/uxarray/uxarray/test/meshfiles/ugrid/geoflow-small/grid.nc'))
38.2k 38.3k 1.00 face_bounds.FaceBounds.track_peakmem_face_bounds(PosixPath('/home/runner/work/uxarray/uxarray/test/meshfiles/ugrid/quad-hexagon/grid.nc'))
337M 337M 1.00 face_bounds.FaceBoundsColdStartRss.peakmem_open_and_bounds(PosixPath('/home/runner/work/uxarray/uxarray/test/meshfiles/mpas/QU/oQU480.231010.nc'))
366M 367M 1.01 face_bounds.FaceBoundsColdStartRss.peakmem_open_and_bounds(PosixPath('/home/runner/work/uxarray/uxarray/test/meshfiles/scrip/outCSne8/outCSne8.nc'))
338M 338M 1.00 face_bounds.FaceBoundsColdStartRss.peakmem_open_and_bounds(PosixPath('/home/runner/work/uxarray/uxarray/test/meshfiles/ugrid/geoflow-small/grid.nc'))
338M 338M 1.00 face_bounds.FaceBoundsColdStartRss.peakmem_open_and_bounds(PosixPath('/home/runner/work/uxarray/uxarray/test/meshfiles/ugrid/quad-hexagon/grid.nc'))
1.15±0.05μs 1.11±0.05μs 0.96 geometry_kernels.AccucrossKernels.time_accucross
2.44±0.09μs 2.48±0.04μs 1.02 geometry_kernels.AccucrossKernels.time_accucross_pair
371±10ns 396±20ns 1.07 geometry_kernels.EFTPrimitives.time_acc_sqrt_re
351±20ns 386±20ns 1.10 geometry_kernels.EFTPrimitives.time_diff_of_products
330±9ns 336±10ns 1.02 geometry_kernels.EFTPrimitives.time_two_prod
335±8ns 331±5ns 0.99 geometry_kernels.EFTPrimitives.time_two_sum
1.48±0.04μs 1.35±0.02μs 0.92 geometry_kernels.GCAConstLatIntersection.time_accux_constlat_kernel
997±30ns 981±50ns 0.98 geometry_kernels.GCAConstLatIntersection.time_gca_const_lat_intersection
1.72±0.06μs 1.69±0.07μs 0.98 geometry_kernels.GCAConstLatIntersection.time_try_gca_const_lat_intersection
36.0±0.6μs 35.0±0.5μs 0.97 geometry_kernels.OrientPredicates.time_on_minor_arc
646±20ns 636±10ns 0.98 geometry_kernels.OrientPredicates.time_orient3d_on_sphere
2.78±0.1ms 2.63±0.02ms 0.95 geometry_samebody.SameBodyConstLat.time_accux_dispatch
1.25±0.01ms 1.26±0.02ms 1.01 geometry_samebody.SameBodyConstLat.time_accux_kernel
1.73±0.02ms 1.70±0.02ms 0.99 geometry_samebody.SameBodyConstLat.time_fp64_dispatch
147±2μs 148±3μs 1.01 geometry_samebody.SameBodyConstLat.time_fp64_kernel
31.9±0.02ms 31.5±0.2ms 0.99 geometry_samebody_gcagca.SameBodyGcaGca.time_accux_dispatch
10.1±0.07ms 10.1±0.03ms 1.00 geometry_samebody_gcagca.SameBodyGcaGca.time_accux_kernel
26.3±0.2ms 26.2±0.2ms 1.00 geometry_samebody_gcagca.SameBodyGcaGca.time_fp64_dispatch
4.17±0.01ms 4.10±0.05ms 0.98 geometry_samebody_gcagca.SameBodyGcaGca.time_fp64_kernel
788±10ms 770±20ms 0.98 import.Imports.timeraw_import_uxarray
293M 293M 1.00 import.Imports.track_peakmem_import_uxarray
2.26±0.02ms 2.28±0.02ms 1.01 mpas_ocean.CheckNorm.time_check_norm('120km')
1.79±0.03ms 1.73±0.02ms 0.97 mpas_ocean.CheckNorm.time_check_norm('480km')
691±6ms 700±9ms 1.01 mpas_ocean.ConnectivityConstruction.time_face_face_connectivity('120km')
44.1±1ms 44.2±0.8ms 1.00 mpas_ocean.ConnectivityConstruction.time_face_face_connectivity('480km')
666±10μs 664±10μs 1.00 mpas_ocean.ConnectivityConstruction.time_n_nodes_per_face('120km')
554±5μs 542±20μs 0.98 mpas_ocean.ConnectivityConstruction.time_n_nodes_per_face('480km')
4.37±0.04ms 4.38±0.05ms 1.00 mpas_ocean.ConstructFaceLatLon.time_cartesian_averaging('120km')
3.30±0.03ms 3.41±0.05ms 1.03 mpas_ocean.ConstructFaceLatLon.time_cartesian_averaging('480km')
99.9±1ms 101±0.8ms 1.01 mpas_ocean.ConstructFaceLatLon.time_welzl('120km')
10.1±0.09ms 9.92±0.2ms 0.99 mpas_ocean.ConstructFaceLatLon.time_welzl('480km')
18.8±0.2ms 18.6±0.3ms 0.99 mpas_ocean.ConstructTreeStructures.time_ball_tree('120km')
1.10±0.01ms 1.10±0.06ms 1.00 mpas_ocean.ConstructTreeStructures.time_ball_tree('480km')
10.2±0.08ms 10.3±0.2ms 1.00 mpas_ocean.ConstructTreeStructures.time_kd_tree('120km')
731±7μs 741±6μs 1.01 mpas_ocean.ConstructTreeStructures.time_kd_tree('480km')
516±10ms 504±2ms 0.98 mpas_ocean.CrossSections.time_const_lat('120km', 1)
257±3ms 259±3ms 1.01 mpas_ocean.CrossSections.time_const_lat('120km', 2)
133±2ms 132±1ms 1.00 mpas_ocean.CrossSections.time_const_lat('120km', 4)
439±9ms 443±2ms 1.01 mpas_ocean.CrossSections.time_const_lat('480km', 1)
225±2ms 222±1ms 0.99 mpas_ocean.CrossSections.time_const_lat('480km', 2)
115±1ms 114±1ms 0.99 mpas_ocean.CrossSections.time_const_lat('480km', 4)
357M 357M 1.00 mpas_ocean.CrossSectionsPeakMem.peakmem_const_lat('120km', 1)
357M 357M 1.00 mpas_ocean.CrossSectionsPeakMem.peakmem_const_lat('120km', 2)
356M 357M 1.00 mpas_ocean.CrossSectionsPeakMem.peakmem_const_lat('120km', 4)
340M 339M 1.00 mpas_ocean.CrossSectionsPeakMem.peakmem_const_lat('480km', 1)
340M 340M 1.00 mpas_ocean.CrossSectionsPeakMem.peakmem_const_lat('480km', 2)
340M 340M 1.00 mpas_ocean.CrossSectionsPeakMem.peakmem_const_lat('480km', 4)
22.0±0.2ms 21.4±0.4ms 0.97 mpas_ocean.DualMesh.time_dual_mesh_construction('120km')
2.53±0.04ms 2.46±0.04ms 0.97 mpas_ocean.DualMesh.time_dual_mesh_construction('480km')
56.8±0.5ms 56.2±1ms 0.99 mpas_ocean.FaceAreas.time_face_areas('120km')
6.35±2000ms 6.26±0.08ms 0.99 mpas_ocean.FaceAreas.time_face_areas('480km')
229k 229k 1.00 mpas_ocean.FaceAreas.track_nbytes_face_areas('120km')
14.3k 14.3k 1.00 mpas_ocean.FaceAreas.track_nbytes_face_areas('480km')
2.12M 2.12M 1.00 mpas_ocean.FaceAreas.track_peakmem_face_areas('120km')
836k 836k 1.00 mpas_ocean.FaceAreas.track_peakmem_face_areas('480km')
821±9ms 801±10ms 0.98 mpas_ocean.GeoDataFrame.time_to_geodataframe('120km', False)
50.2±1ms 49.6±0.9ms 0.99 mpas_ocean.GeoDataFrame.time_to_geodataframe('120km', True)
72.1±0.8ms 71.1±0.6ms 0.99 mpas_ocean.GeoDataFrame.time_to_geodataframe('480km', False)
5.94±0.2ms 5.49±0.1ms 0.92 mpas_ocean.GeoDataFrame.time_to_geodataframe('480km', True)
167±5ms 164±10ms 0.98 mpas_ocean.Gradient.time_gradient('120km')
11.5±0.7ms 11.0±0.1ms 0.96 mpas_ocean.Gradient.time_gradient('480km')
457k 457k 1.00 mpas_ocean.Gradient.track_nbytes_gradient('120km')
28.7k 28.7k 1.00 mpas_ocean.Gradient.track_nbytes_gradient('480km')
5.08M 5.08M 1.00 mpas_ocean.Gradient.track_peakmem_gradient('120km')
328k 328k 1.00 mpas_ocean.Gradient.track_peakmem_gradient('480km')
352M 352M 1.00 mpas_ocean.GradientColdStartRss.peakmem_gradient('120km')
331M 331M 1.00 mpas_ocean.GradientColdStartRss.peakmem_gradient('480km')
385±30μs 361±10μs 0.94 mpas_ocean.HoleEdgeIndices.time_construct_hole_edge_indices('120km')
568±30μs 552±20μs 0.97 mpas_ocean.Integrate.time_integrate('120km')
509±50μs 456±10μs ~0.89 mpas_ocean.Integrate.time_integrate('480km')
18.4M 18.4M 1.00 mpas_ocean.Integrate.track_nbytes_integrate('120km')
1.2M 1.2M 1.00 mpas_ocean.Integrate.track_nbytes_integrate('480km')
186±5ms 181±2ms 0.97 mpas_ocean.MatplotlibConversion.time_dataarray_to_polycollection('120km', 'exclude')
186±1ms 184±3ms 0.99 mpas_ocean.MatplotlibConversion.time_dataarray_to_polycollection('120km', 'include')
184±2ms 178±1ms 0.97 mpas_ocean.MatplotlibConversion.time_dataarray_to_polycollection('120km', 'split')
14.6±0.8ms 13.2±0.1ms ~0.90 mpas_ocean.MatplotlibConversion.time_dataarray_to_polycollection('480km', 'exclude')
14.0±0.7ms 13.0±0.1ms 0.93 mpas_ocean.MatplotlibConversion.time_dataarray_to_polycollection('480km', 'include')
13.8±0.6ms 13.8±0.1ms 1.00 mpas_ocean.MatplotlibConversion.time_dataarray_to_polycollection('480km', 'split')
379±20μs 344±9μs ~0.91 mpas_ocean.PointInPolygon.time_face_search_lonlat('120km')
344±20μs 343±20μs 1.00 mpas_ocean.PointInPolygon.time_face_search_lonlat('480km')
324±9μs 318±10μs 0.98 mpas_ocean.PointInPolygon.time_face_search_xyz('120km')
328±20μs 322±10μs 0.98 mpas_ocean.PointInPolygon.time_face_search_xyz('480km')
192±0.03ms 195±0.8ms 1.02 mpas_ocean.RemapDownsample.time_bilinear_remapping
215±2ms 217±1ms 1.01 mpas_ocean.RemapDownsample.time_inverse_distance_weighted_remapping
14.9±0.3ms 15.0±0.1ms 1.01 mpas_ocean.RemapDownsample.time_nearest_neighbor_remapping
1.10±0.02s 1.08±0.01s 0.98 mpas_ocean.RemapUpsample.time_bilinear_remapping
46.2±1ms 45.3±0.8ms 0.98 mpas_ocean.RemapUpsample.time_inverse_distance_weighted_remapping
11.1±0.3ms 10.9±0.2ms 0.99 mpas_ocean.RemapUpsample.time_nearest_neighbor_remapping
8.79±0.5ms 8.37±0.1ms 0.95 mpas_ocean.ZonalAverage.time_zonal_average('120km')
4.24±0.1ms 4.14±0.06ms 0.98 mpas_ocean.ZonalAverage.time_zonal_average('480km')
358M 358M 1.00 mpas_ocean.ZonalAveragePeakMem.peakmem_zonal_average('120km')
341M 342M 1.00 mpas_ocean.ZonalAveragePeakMem.peakmem_zonal_average('480km')
6.03±0.2ms 5.91±0.07ms 0.98 quad_hexagon.QuadHexagon.time_open_dataset
5.14±0.2ms 5.13±0.04ms 1.00 quad_hexagon.QuadHexagon.time_open_grid
408 408 1.00 quad_hexagon.QuadHexagon.track_nbytes_open_dataset
392 392 1.00 quad_hexagon.QuadHexagon.track_nbytes_open_grid
73.8k 73.5k 1.00 quad_hexagon.QuadHexagon.track_peakmem_open_dataset
72.8k 72.7k 1.00 quad_hexagon.QuadHexagon.track_peakmem_open_grid

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

run-benchmark Run ASV benchmark workflow scalability Related to scalability & performance efforts

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Scalarizing gca_gca_intersection

1 participant