mirror of
https://github.com/JuliaFEM/JuliaFEM.jl.git
synced 2026-09-23 02:59:52 +00:00
302 lines
8.2 KiB
Julia
302 lines
8.2 KiB
Julia
|
|
# Benchmark: Integration Point Access Patterns
|
||
|
|
# ============================================
|
||
|
|
#
|
||
|
|
# This benchmark compares different approaches to storing and accessing
|
||
|
|
# integration points in finite element assembly loops.
|
||
|
|
#
|
||
|
|
# Key Question: What's the fastest way to get integration points?
|
||
|
|
#
|
||
|
|
# Approaches tested:
|
||
|
|
# 1. OLD: Runtime dispatch + mutable struct with Dict (type-unstable)
|
||
|
|
# 2. NEW Option A: Compile-time function (like eval_basis!)
|
||
|
|
# 3. NEW Option B: Store in element as NTuple
|
||
|
|
# 4. NEW Option C: Hybrid (compile-time generation + caching)
|
||
|
|
|
||
|
|
using BenchmarkTools
|
||
|
|
using Tensors
|
||
|
|
using StaticArrays
|
||
|
|
|
||
|
|
# ============================================================================
|
||
|
|
# OLD APPROACH: Runtime dispatch with mutable struct
|
||
|
|
# ============================================================================
|
||
|
|
|
||
|
|
struct OldIP
|
||
|
|
id::UInt
|
||
|
|
weight::Float64
|
||
|
|
coords::Tuple{Vararg{Float64}}
|
||
|
|
fields::Dict{String,Any} # Type-unstable!
|
||
|
|
end
|
||
|
|
|
||
|
|
function get_integration_points_old(::Type{Val{:Tri3}})
|
||
|
|
# Simulate runtime dispatch to get IPs
|
||
|
|
return [
|
||
|
|
OldIP(UInt(1), 0.5, (1 / 3, 1 / 3), Dict{String,Any}()),
|
||
|
|
]
|
||
|
|
end
|
||
|
|
|
||
|
|
function assembly_loop_old()
|
||
|
|
sum_val = 0.0
|
||
|
|
for _ in 1:1000 # Simulate 1000 elements
|
||
|
|
ips = get_integration_points_old(Val{:Tri3})
|
||
|
|
for ip in ips
|
||
|
|
w = ip.weight
|
||
|
|
xi, eta = ip.coords
|
||
|
|
# Simulate some computation
|
||
|
|
sum_val += w * (xi + eta)
|
||
|
|
end
|
||
|
|
end
|
||
|
|
return sum_val
|
||
|
|
end
|
||
|
|
|
||
|
|
# ============================================================================
|
||
|
|
# NEW OPTION A: Compile-time function (zero allocation)
|
||
|
|
# ============================================================================
|
||
|
|
|
||
|
|
"""
|
||
|
|
Return integration points as tuple at compile time.
|
||
|
|
Similar to eval_basis! - zero allocation, fully inlined.
|
||
|
|
"""
|
||
|
|
@inline function get_integration_points!(::Type{Val{:Tri3_Gauss1}})
|
||
|
|
# Return as tuple of (weight, coords) pairs
|
||
|
|
return (
|
||
|
|
(0.5, (1 / 3, 1 / 3)),
|
||
|
|
)
|
||
|
|
end
|
||
|
|
|
||
|
|
@inline function get_integration_points!(::Type{Val{:Tet4_Gauss1}})
|
||
|
|
return (
|
||
|
|
(1 / 24, (0.25, 0.25, 0.25)),
|
||
|
|
)
|
||
|
|
end
|
||
|
|
|
||
|
|
function assembly_loop_option_a()
|
||
|
|
sum_val = 0.0
|
||
|
|
for _ in 1:1000
|
||
|
|
ips = get_integration_points!(Val{:Tri3_Gauss1})
|
||
|
|
for (w, (xi, eta)) in ips
|
||
|
|
sum_val += w * (xi + eta)
|
||
|
|
end
|
||
|
|
end
|
||
|
|
return sum_val
|
||
|
|
end
|
||
|
|
|
||
|
|
# ============================================================================
|
||
|
|
# NEW OPTION B: Store in element as NTuple (what we have now)
|
||
|
|
# ============================================================================
|
||
|
|
|
||
|
|
struct IntegrationPoint{D}
|
||
|
|
ξ::NTuple{D,Float64}
|
||
|
|
weight::Float64
|
||
|
|
end
|
||
|
|
|
||
|
|
struct MockElement{NIP}
|
||
|
|
ips::NTuple{NIP,IntegrationPoint{2}}
|
||
|
|
end
|
||
|
|
|
||
|
|
function create_element_b()
|
||
|
|
ips = (
|
||
|
|
IntegrationPoint((1 / 3, 1 / 3), 0.5),
|
||
|
|
)
|
||
|
|
return MockElement(ips)
|
||
|
|
end
|
||
|
|
|
||
|
|
function assembly_loop_option_b()
|
||
|
|
elements = [create_element_b() for _ in 1:1000]
|
||
|
|
sum_val = 0.0
|
||
|
|
for element in elements
|
||
|
|
for ip in element.ips
|
||
|
|
w = ip.weight
|
||
|
|
xi, eta = ip.ξ
|
||
|
|
sum_val += w * (xi + eta)
|
||
|
|
end
|
||
|
|
end
|
||
|
|
return sum_val
|
||
|
|
end
|
||
|
|
|
||
|
|
# ============================================================================
|
||
|
|
# NEW OPTION C: Compile-time with Tensors.jl Vec (recommended for FEM)
|
||
|
|
# ============================================================================
|
||
|
|
|
||
|
|
"""
|
||
|
|
Return integration points with Vec{D} coordinates (Tensors.jl).
|
||
|
|
This matches the golden standard from nodal assembly demos.
|
||
|
|
"""
|
||
|
|
@inline function get_integration_points_vec!(::Type{Val{:Tri3_Gauss1}})
|
||
|
|
return (
|
||
|
|
(0.5, Vec{2}((1 / 3, 1 / 3))),
|
||
|
|
)
|
||
|
|
end
|
||
|
|
|
||
|
|
@inline function get_integration_points_vec!(::Type{Val{:Tet4_Gauss1}})
|
||
|
|
return (
|
||
|
|
(1 / 24, Vec{3}((0.25, 0.25, 0.25))),
|
||
|
|
)
|
||
|
|
end
|
||
|
|
|
||
|
|
function assembly_loop_option_c()
|
||
|
|
sum_val = 0.0
|
||
|
|
for _ in 1:1000
|
||
|
|
ips = get_integration_points_vec!(Val{:Tri3_Gauss1})
|
||
|
|
for (w, xi) in ips
|
||
|
|
# Vec arithmetic is optimized by Tensors.jl
|
||
|
|
sum_val += w * sum(xi)
|
||
|
|
end
|
||
|
|
end
|
||
|
|
return sum_val
|
||
|
|
end
|
||
|
|
|
||
|
|
# ============================================================================
|
||
|
|
# NEW OPTION D: Pre-generated global constants (ultimate zero-cost)
|
||
|
|
# ============================================================================
|
||
|
|
|
||
|
|
const TRI3_GAUSS1_IPS = (
|
||
|
|
(0.5, Vec{2}((1 / 3, 1 / 3))),
|
||
|
|
)
|
||
|
|
|
||
|
|
const TET4_GAUSS1_IPS = (
|
||
|
|
(1 / 24, Vec{3}((0.25, 0.25, 0.25))),
|
||
|
|
)
|
||
|
|
|
||
|
|
function assembly_loop_option_d()
|
||
|
|
sum_val = 0.0
|
||
|
|
for _ in 1:1000
|
||
|
|
for (w, xi) in TRI3_GAUSS1_IPS
|
||
|
|
sum_val += w * sum(xi)
|
||
|
|
end
|
||
|
|
end
|
||
|
|
return sum_val
|
||
|
|
end
|
||
|
|
|
||
|
|
# ============================================================================
|
||
|
|
# OPTION E: Hybrid - Function returns pre-computed constant
|
||
|
|
# ============================================================================
|
||
|
|
|
||
|
|
@inline get_ips_tri3_gauss1() = TRI3_GAUSS1_IPS
|
||
|
|
@inline get_ips_tet4_gauss1() = TET4_GAUSS1_IPS
|
||
|
|
|
||
|
|
function assembly_loop_option_e()
|
||
|
|
sum_val = 0.0
|
||
|
|
for _ in 1:1000
|
||
|
|
for (w, xi) in get_ips_tri3_gauss1()
|
||
|
|
sum_val += w * sum(xi)
|
||
|
|
end
|
||
|
|
end
|
||
|
|
return sum_val
|
||
|
|
end
|
||
|
|
|
||
|
|
# ============================================================================
|
||
|
|
# Run Benchmarks
|
||
|
|
# ============================================================================
|
||
|
|
|
||
|
|
println("="^80)
|
||
|
|
println("Integration Point Access Pattern Benchmark")
|
||
|
|
println("="^80)
|
||
|
|
println()
|
||
|
|
|
||
|
|
println("OLD APPROACH: Runtime dispatch + mutable struct with Dict")
|
||
|
|
println("-"^80)
|
||
|
|
@btime assembly_loop_old()
|
||
|
|
println()
|
||
|
|
|
||
|
|
println("OPTION A: Compile-time function returning tuples")
|
||
|
|
println("-"^80)
|
||
|
|
@btime assembly_loop_option_a()
|
||
|
|
println()
|
||
|
|
|
||
|
|
println("OPTION B: Store in element as NTuple (current approach)")
|
||
|
|
println("-"^80)
|
||
|
|
@btime assembly_loop_option_b()
|
||
|
|
println()
|
||
|
|
|
||
|
|
println("OPTION C: Compile-time function with Vec{D} (Tensors.jl)")
|
||
|
|
println("-"^80)
|
||
|
|
@btime assembly_loop_option_c()
|
||
|
|
println()
|
||
|
|
|
||
|
|
println("OPTION D: Pre-generated global constants")
|
||
|
|
println("-"^80)
|
||
|
|
@btime assembly_loop_option_d()
|
||
|
|
println()
|
||
|
|
|
||
|
|
println("OPTION E: Function returning pre-computed constant")
|
||
|
|
println("-"^80)
|
||
|
|
@btime assembly_loop_option_e()
|
||
|
|
println()
|
||
|
|
|
||
|
|
# ============================================================================
|
||
|
|
# Realistic FEM Assembly Benchmark
|
||
|
|
# ============================================================================
|
||
|
|
|
||
|
|
println()
|
||
|
|
println("="^80)
|
||
|
|
println("REALISTIC FEM ASSEMBLY COMPARISON")
|
||
|
|
println("="^80)
|
||
|
|
println()
|
||
|
|
|
||
|
|
# Simulate realistic element stiffness computation
|
||
|
|
function compute_element_stiffness_old(element_type::Type{Val{:Tri3}})
|
||
|
|
K_elem = zeros(6, 6)
|
||
|
|
ips = get_integration_points_old(element_type)
|
||
|
|
for ip in ips
|
||
|
|
w = ip.weight
|
||
|
|
xi, eta = ip.coords
|
||
|
|
# Simulate shape function evaluation and stiffness computation
|
||
|
|
N1 = 1 - xi - eta
|
||
|
|
N2 = xi
|
||
|
|
N3 = eta
|
||
|
|
# Accumulate (simplified)
|
||
|
|
K_elem[1, 1] += w * (N1^2)
|
||
|
|
end
|
||
|
|
return K_elem[1, 1]
|
||
|
|
end
|
||
|
|
|
||
|
|
function compute_element_stiffness_new()
|
||
|
|
K_elem = 0.0
|
||
|
|
for (w, xi) in get_integration_points_vec!(Val{:Tri3_Gauss1})
|
||
|
|
# Vec arithmetic
|
||
|
|
xi_val = xi[1]
|
||
|
|
eta_val = xi[2]
|
||
|
|
N1 = 1 - xi_val - eta_val
|
||
|
|
K_elem += w * (N1^2)
|
||
|
|
end
|
||
|
|
return K_elem
|
||
|
|
end
|
||
|
|
|
||
|
|
println("OLD: Realistic element stiffness assembly")
|
||
|
|
@btime begin
|
||
|
|
sum_val = 0.0
|
||
|
|
for _ in 1:1000
|
||
|
|
sum_val += compute_element_stiffness_old(Val{:Tri3})
|
||
|
|
end
|
||
|
|
sum_val
|
||
|
|
end
|
||
|
|
|
||
|
|
println()
|
||
|
|
println("NEW: Realistic element stiffness assembly")
|
||
|
|
@btime begin
|
||
|
|
sum_val = 0.0
|
||
|
|
for _ in 1:1000
|
||
|
|
sum_val += compute_element_stiffness_new()
|
||
|
|
end
|
||
|
|
sum_val
|
||
|
|
end
|
||
|
|
|
||
|
|
println()
|
||
|
|
println("="^80)
|
||
|
|
println("SUMMARY")
|
||
|
|
println("="^80)
|
||
|
|
println()
|
||
|
|
println("Expected ranking (fastest to slowest):")
|
||
|
|
println("1. Option D/E: Pre-computed constants (ultimate zero-cost)")
|
||
|
|
println("2. Option C: Compile-time with Vec{D} (recommended for FEM)")
|
||
|
|
println("3. Option A: Compile-time with plain tuples")
|
||
|
|
println("4. Option B: Stored in element NTuple (slight overhead)")
|
||
|
|
println("5. OLD: Runtime dispatch + Dict (type-unstable)")
|
||
|
|
println()
|
||
|
|
println("RECOMMENDATION:")
|
||
|
|
println(" Use Option C or D/E for integration points:")
|
||
|
|
println(" - Compile-time generation like eval_basis!")
|
||
|
|
println(" - Return as Tuple of (weight, Vec{D}) pairs")
|
||
|
|
println(" - Zero allocation, fully inlined")
|
||
|
|
println(" - Matches golden standard architecture")
|