Skip to content

Allocations with StaticArrays in Julia v1.11 #1282

Open
JuliaLang/julia
#61776

Description

@kaipartmann

I am the main author of the package Peridynamics.jl and recently changed to Julia v1.11.
With the new Julia version, a significant increase in the simulation time can be found when using a certain material.

Peridynamics.jl Benchmark

Please see the following benchmark, which is also described in more detail in the tutorials (Tutorial mode I fracture).

using Peridynamics

function mode_i(mat, npyz)
    l, Δx, δ, a = 1.0, 1/npyz, 3.015/npyz, 0.5
    pos, vol = uniform_box(l, l, 0.1l, Δx)
    ids = sortperm(pos[2,:])
    body = Body(mat, pos[:, ids], vol[ids])
    material!(body; horizon=3.015Δx, E=2.1e5, nu=0.25, rho=8e-6, Gc=2.7)
    point_set!(p -> p[1] ≤ -l/2+a && 0 ≤ p[2] ≤ 2δ, body, :set_a)
    point_set!(p -> p[1] ≤ -l/2+a && -2δ ≤ p[2] < 0, body, :set_b)
    precrack!(body, :set_a, :set_b)
    point_set!(p -> p[2] > l/2-Δx, body, :set_top)
    point_set!(p -> p[2] < -l/2+Δx, body, :set_bottom)
    velocity_bc!(t -> -30, body, :set_bottom, :y)
    velocity_bc!(t -> 30, body, :set_top, :y)
    vv = VelocityVerlet(steps=2000)
    job = Job(body, vv; path=joinpath(@__DIR__, "results", "mode_i"))
    @time submit(job)
    return nothing
end

mode_i(NOSBMaterial(), 25) # compilation
mode_i(NOSBMaterial(), 50) # work
❯ julia +1.10.5 --project -t 6 mode_i_benchmark.jl
  0.719477 seconds (1.39 M allocations: 450.082 MiB, 2.77% gc time, 74.16% compilation time)
  6.090774 seconds (1.28 M allocations: 1.118 GiB, 1.06% gc time, 2.17% compilation time)

❯ julia +1.11.1 --project -t 6 mode_i_benchmark.jl
  9.574071 seconds (2.15 G allocations: 82.415 GiB, 15.59% gc time, 6.09% compilation time)
177.261652 seconds (40.34 G allocations: 1.501 TiB, 23.15% gc time)

The same simulation takes 29x longer on v1.11.1 than v1.10.5.

When investigating the problem, I narrowed it down to the calculation of the deformation gradient:
https://github.com/kaipartmann/Peridynamics.jl/blob/f9b8fe60e743d828cf96d2366610c6a69d7ac054/src/physics/correspondence.jl#L174-L194

MWE

The same behavior can be reproduced with a small MWE:

using LinearAlgebra, StaticArrays, BenchmarkTools

function mwe1(a, X, n)
    K = zeros(SMatrix{3,3})
    for i in 1:n
        k = a * i
        K += k * X * X'
    end
    return K
end

@btime mwe1(1e-5, SVector{3}(1.0, 1.0, 1.0), 1_000_000);
❯ julia +1.10.5 --project -t 6 mwe.jl 
  1.070 ms (0 allocations: 0 bytes)

❯ julia +1.11.1 --project -t 6 mwe.jl 
  129.890 ms (7000000 allocations: 289.92 MiB)

I think the problem is related to allocations when using += with a SMatrix or SVector.

As a quick fix, rewriting the calculation by hand leads to even slightly better performance on v1.11:

function mwe2(a, X, n)
    K11, K12, K13 = 0.0, 0.0, 0.0
    K21, K22, K23 = 0.0, 0.0, 0.0
    K31, K32, K33 = 0.0, 0.0, 0.0
    X1, X2, X3 = X[1], X[2], X[3]
    for i in 1:n
        k = a * i
        K11 += k * X1 * X1
        K12 += k * X1 * X2
        K13 += k * X1 * X3
        K21 += k * X2 * X1
        K22 += k * X2 * X2
        K23 += k * X2 * X3
        K31 += k * X3 * X1
        K32 += k * X3 * X2
        K33 += k * X3 * X3
    end
    K = SMatrix{3,3}(K11, K21, K31, K12, K22, K32, K13, K23, K33)
    return K
end

@btime mwe2(1e-5, SVector{3}(1.0, 1.0, 1.0), 1_000_000);
❯ julia +1.10.5 --project -t 6 mwe.jl 
  1.211 ms (0 allocations: 0 bytes)

❯ julia +1.11.1 --project -t 6 mwe.jl 
  1.072 ms (0 allocations: 0 bytes)

Activity

  1. changed the title [-]Allocations with StaticArrays in Julia v1.11[/-] [+]Allocations with StaticArrays and `+=` in Julia v1.11[/+] on Oct 22, 2024
  2. mateuszbaran commented on Oct 22, 2024

    @mateuszbaran
    Collaborator

    Interesting. This looks like a regression in Julia compiler. I'd have expected call-site @inline to fix this but it does not.

  3. KristofferC commented on Oct 22, 2024

    @KristofferC
    Contributor

    I think this should have a corresponding issue in the julia repo since it is probably a regression there.

  4. changed the title [-]Allocations with StaticArrays and `+=` in Julia v1.11[/-] [+]Allocations with StaticArrays in Julia v1.11[/+] on Oct 22, 2024
  5. kaipartmann commented on Oct 22, 2024

    @kaipartmann
    Author

    @KristofferC I opened an issue in the julia repo.
    Interestingly, the problem can be solved by changing k * X * X' to k * (X * X'):

    function mwe3(a, X, n)
        K = zeros(SMatrix{3,3})
        for i in 1:n
            k = a * i
            K += k * (X * X')
        end
        return K
    end
    @btime mwe3(1e-5, SVector{3}(1.0, 1.0, 1.0), 1_000_000);
    ❯ julia +1.10.5 --project -t 6 mwe.jl 
      805.458 μs (0 allocations: 0 bytes)
    
    ❯ julia +1.11.1 --project -t 6 mwe.jl
      708.208 μs (0 allocations: 0 bytes)

    Since the problem is likely not related to +=, I changed the title accordingly.

  6. mcabbott commented on Oct 25, 2024

    @mcabbott
    Collaborator

    Before Julia 1.7, I believe this would have done K += (k * X) * X', which also seems fast now.

    After 3-arg *, this calls K += broadcast(*, k, X, X'), which is slow, like mwe1.

    Strangely, using K += k .* X .* X' seems to be fast, like mwe3.

  7. efaulhaber commented on Apr 27, 2026

    @efaulhaber

    Any news on this? It seems that I am experiencing the same issue, which I broke down to this MWE:

    julia> using StaticArrays, BenchmarkTools
    
    julia> function test1(a, x)
               return (a * x * transpose(x)) * x
           end;
    
    julia> a = rand(); x2 = SVector(rand(), rand()); x3 = SVector(rand(), rand(), rand());
    
    julia> @benchmark test1($a, $x2)
    BenchmarkTools.Trial: 10000 samples with 1000 evaluations per sample.
     Range (min … max):  1.917 ns … 28.125 ns  ┊ GC (min … max): 0.00% … 0.00%
     Time  (median):     2.000 ns              ┊ GC (median):    0.00%
     Time  (mean ± σ):   2.050 ns ±  0.488 ns  ┊ GC (mean ± σ):  0.00% ± 0.00%
    
         ▁   █  ▆   ▃   ▃  ▁                                     ▁
      ▄▁▁█▁▁▁█▁▁█▁▁▁█▁▁▁█▁▁█▁▁▁▇▁▁▆▁▁▁▅▁▁▁▅▁▁▅▁▁▁▅▁▁▁▃▁▁▅▁▁▁█▁▁▅ █
      1.92 ns      Histogram: log(frequency) by time     2.58 ns <
    
     Memory estimate: 0 bytes, allocs estimate: 0.
    
    julia> @benchmark test1($a, $x3)
    BenchmarkTools.Trial: 10000 samples with 685 evaluations per sample.
     Range (min … max):  184.915 ns …   6.870 μs  ┊ GC (min … max): 0.00% … 96.45%
     Time  (median):     199.270 ns               ┊ GC (median):    0.00%
     Time  (mean ± σ):   221.310 ns ± 240.758 ns  ┊ GC (mean ± σ):  8.51% ±  7.37%
    
        ▂▄▆▆▆█▇▅▄▄▄▃▁▁                                               
      ▃▆███████████████▇▇▇▆▅▅▄▄▄▃▃▃▃▂▂▂▂▂▂▂▂▂▂▂▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ ▃
      185 ns           Histogram: frequency by time          264 ns <
    
     Memory estimate: 400 bytes, allocs estimate: 10.

    This does not happen with permutedims or different orders of parentheses.

  8. adienes commented on May 13, 2026

    @adienes
    Contributor

    looks like it was indeed an inlining problem

  9. added a commit that references this issue on May 14, 2026
  10. adienes commented on Oct 19, 2026

    @adienes
    Contributor

    I don't have permissions but this should be reopened

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

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