Skip to content

the performance of array construction from numbers: SparseArrays piracy and hvcat overhead #58397

Open
@johnnychen94

Description

@johnnychen94

In julia we have two typical ways to concatenate arrays,

  • [] cat syntax. This is used most frequently due to its simplicity
  • array initialization: out = Array{...}(undef, ...); ... This has the least overhead but it's often too verbose to write it

Let's first give the conclusion and the to-do task list:

Conclusions:

  • [] syntax is always slower than the array init method. And [x x; y y] matrix concatenation is too slow to be acceptable.
  • Type piracy in SparseArrays affected the performance badly since Julia 1.10
  • Julia 1.11 and 1.12 have brought a significant performance boost 🎉 🚀

Todo:

  • the performance gap between [x x; y y] and array init method should be narrowed. (especially for Julia 1.12)
  • We need to eliminate the performance regression brought by using SparseArrays

Because BenchmarkTools depends on SparseArrays (indirectly) in some Julia versions, I use the following script to run the benchmark:

function many_loop(f, n)
    for i in 1:n
        f()
    end
end

f1_row(x, y) = hcat(x, y, x, y)
function f2_row(x, y)
    c = Matrix{Float64}(undef, 1, 4)
    @inbounds begin
        c[1] = x
        c[2] = y
        c[3] = x
        c[4] = y
    end
    return c
end

f1_col(x, y) = vcat(x, y, x, y)
function f2_col(x, y)
    c = Matrix{Float64}(undef, 4, 1)
    @inbounds begin
        c[1] = x
        c[2] = y
        c[3] = x
        c[4] = y
    end
    return c
end

f1_mat(x, y) = hvcat((2, 2), x, y, x, y)
function f2_mat(x, y)
    c = Matrix{Float64}(undef, 2, 2)
    @inbounds begin
        c[1] = x
        c[2] = x
        c[3] = y
        c[4] = y
    end
    return c
end


@assert vec(f1_row(0.3, 0.4)) == vec(f2_row(0.3, 0.4))
@assert vec(f1_col(0.3, 0.4)) == vec(f2_col(0.3, 0.4))
@assert f1_mat(0.3, 0.4) == f2_mat(0.3, 0.4)
many_loop(()->f1_row(0.3, 0.4), 1000000)

function f()
    x, y = rand(), rand()

    @info "row concatenation"
    @time many_loop(()->f1_row(x, y), 1000000)
    @time many_loop(()->f2_row(x, y), 1000000)

    @info "column concatenation"
    @time many_loop(()->f1_col(x, y), 1000000)
    @time many_loop(()->f2_col(x, y), 1000000)

    @info "matrix concatenation"
    @time many_loop(()->f1_mat(x, y), 1000000)
    @time many_loop(()->f2_mat(x, y), 1000000)
end

f()
using SparseArrays

@info "SparseArrays loaded"
f()
julia method version time (ms) time (ms) (after SparseArrays)
1.9.3 row native 29.235 26.248
1.9.3 row init 25.602 24.399
1.9.3 column native 24.030 22.598
1.9.3 column init 25.017 23.616
1.9.3 matrix native 68.152 63.910
1.9.3 matrix init 23.486 24.113
1.10.9 row native 28.702 28.451
1.10.9 row init 24.184 25.074
1.10.9 column native 24.178 22.774
1.10.9 column init 24.048 22.900
1.10.9 matrix native 47.608 334.611
1.10.9 matrix init 22.945 23.823
1.11.5 row native 25.212 19.552
1.11.5 row init 16.931 16.823
1.11.5 column native 16.209 18.608
1.11.5 column init 16.295 18.340
1.11.5 matrix native 49.707 157.885
1.11.5 matrix init 16.765 17.138
1.12.0-beta3 row native 0.224 0.201
1.12.0-beta3 row init 0.354 0.666
1.12.0-beta3 column native 0.338 0.644
1.12.0-beta3 column init 0.339 0.647
1.12.0-beta3 matrix native 151.048 26.780
1.12.0-beta3 matrix init 0.338 0.782
Julia Version 1.11.5
Commit 760b2e5b739 (2025-04-14 06:53 UTC)
Build Info:
  Official https://julialang.org/ release
Platform Info:
  OS: Linux (x86_64-linux-gnu)
  CPU: 20 × 12th Gen Intel(R) Core(TM) i7-12700H
  WORD_SIZE: 64
  LLVM: libLLVM-16.0.6 (ORCJIT, alderlake)
Threads: 1 default, 0 interactive, 1 GC (on 20 virtual cores)

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions