From 55c36e072b3507b952a961eba0ae413edcaf6816 Mon Sep 17 00:00:00 2001 From: Jukka Aho Date: Sat, 9 May 2026 18:37:46 +0300 Subject: [PATCH] test(assemblers): add partitioning zero-alloc regression --- .../test_partitioning_zero_alloc.jl | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 test/assemblers/test_partitioning_zero_alloc.jl diff --git a/test/assemblers/test_partitioning_zero_alloc.jl b/test/assemblers/test_partitioning_zero_alloc.jl new file mode 100644 index 0000000..b84a6d8 --- /dev/null +++ b/test/assemblers/test_partitioning_zero_alloc.jl @@ -0,0 +1,64 @@ +# This file is a part of JuliaFEM. +# License is MIT: see https://github.com/JuliaFEM/JuliaFEM.jl/blob/master/LICENSE.md + +""" +Partition staging buffers (`BitVector` masks + scratch vectors) must stay +allocation-free in tight loops once workspace is allocated — same contract as +the DOF-based assembler regressions. +""" + +using Test +using InteractiveUtils: @allocated +using JuliaFEM +using JuliaFEM: create_elements!, @DOFSet, DOF, Displacement, Vertex +using JuliaFEM: brick_hex_partition_slabs, element_indices_for_part +using JuliaFEM: sum_element_dof_slots, mark_referenced_dofs!, collect_true_indices! +using JuliaFEM: fill_referenced_dof_indices!, ghost_dof_mask!, node_partition_owner_min! +using JuliaFEM: mark_owned_vertex_field_dofs! + +@testset "partitioning workspace hot path — zero allocations" begin + nx, ny, nz = 4, 3, 3 + mesh = create_structured_box_mesh(Hex8; nx = nx, ny = ny, nz = nz) + S = @DOFSet{u::DOF{Displacement{3}, Vertex}} + elements, handler = create_elements!(mesh, Element{Hex8, Lagrange{1}, S}) + layout = brick_hex_partition_slabs(nx, ny, nz, 3; axis = :z) + ndof = handler.total_dofs + nnodes = length(mesh.nodes) + + ep = element_indices_for_part(layout, 2) + @test sum_element_dof_slots(elements, ep) > 0 + + mask_a = falses(ndof) + mask_b = falses(ndof) + mask_c = falses(ndof) + dof_buf = Vector{Int}(undef, ndof) + node_own = Vector{Int}(undef, nnodes) + + node_partition_owner_min!(node_own, layout, mesh) + mark_referenced_dofs!(mask_a, elements, ep, ndof) + collect_true_indices!(dof_buf, mask_a) + fill_referenced_dof_indices!(dof_buf, mask_b, elements, ep, ndof) + mark_owned_vertex_field_dofs!(mask_b, handler, node_own, 2) + ghost_dof_mask!(mask_c, mask_a, mask_b) + + # Warm LLVM / caches + for _ in 1:3 + fill!(mask_a, false) + mark_referenced_dofs!(mask_a, elements, ep, ndof) + collect_true_indices!(dof_buf, mask_a) + fill!(mask_b, false) + fill_referenced_dof_indices!(dof_buf, mask_b, elements, ep, ndof) + node_partition_owner_min!(node_own, layout, mesh) + fill!(mask_b, false) + mark_owned_vertex_field_dofs!(mask_b, handler, node_own, 2) + ghost_dof_mask!(mask_c, mask_a, mask_b) + end + + @test (@allocated mark_referenced_dofs!(mask_a, elements, ep, ndof)) == 0 + @test (@allocated collect_true_indices!(dof_buf, mask_a)) == 0 + @test (@allocated fill_referenced_dof_indices!(dof_buf, mask_b, elements, ep, ndof)) == 0 + @test (@allocated node_partition_owner_min!(node_own, layout, mesh)) == 0 + @test (@allocated mark_owned_vertex_field_dofs!(mask_b, handler, node_own, 2)) == 0 + @test (@allocated ghost_dof_mask!(mask_c, mask_a, mask_b)) == 0 + @test (@allocated sum_element_dof_slots(elements, ep)) == 0 +end