Skip to content

Missing documentation: sortperm! is not truly non-allocating unless scratchspace is provided #53834

@AhmedSalih3d

Description

@AhmedSalih3d

Hi!

Full thread here:

https://discourse.julialang.org/t/why-does-sortperm-allocate-here/112028/3

Doing a small example it is easy to see that sortperm! will allocate:

function main()
    Cartesians    = Vector{CartesianIndex{2}}(undef, 100)
    SortedIndices = collect(LinearIndices(Cartesians))

    # Fill the array with random CartesianIndex{2} values
    for i in 1:100
        # Assuming you want indices in the range 1:10 for both dimensions
        Cartesians[i] = CartesianIndex(rand(1:10), rand(1:10))
    end

    for iter = 1:5
        b = @allocated sortperm!(SortedIndices, Cartesians)
        println("Iteration ", iter, " : ", b , " allocated bytes")
    end
end

main()

Iteration 1 : 896 allocated bytes
Iteration 2 : 896 allocated bytes
Iteration 3 : 896 allocated bytes
Iteration 4 : 896 allocated bytes
Iteration 5 : 896 allocated bytes

This is because a scratch-space has not been preallocated. Dan kindly provided the following code which fixes the issue:

function main()
    Cartesians    = Vector{CartesianIndex{2}}(undef, 10000)
    SortedIndices = collect(LinearIndices(Cartesians))
    _, t = Base.Sort.make_scratch(nothing, eltype(SortedIndices), length(Cartesians))

    # Fill the array with random CartesianIndex{2} values
    for i in 1:100
        # Assuming you want indices in the range 1:10 for both dimensions
        Cartesians[i] = CartesianIndex(rand(1:10), rand(1:10))
    end

    for iter = 1:5
        b = @allocated sortperm!(SortedIndices, Cartesians; scratch=t)
        println("Iteration ", iter, " : ", b , " allocated bytes")
    end
end 

I would not have been able to discover this without help since the ? sortperm! returns:

sortperm!(ix, A; alg::Algorithm=DEFAULT_UNSTABLE, lt=isless, by=identity, rev::Bool=false, order::Ordering=Forward, [dims::Integer])

  Like sortperm, but accepts a preallocated index vector or array ix with the same axes as A. ix is initialized to contain the values LinearIndices(A).

  │ Warning
  │
  │  Behavior can be unexpected when any mutated argument shares memory with any other argument.

  │ Julia 1.9
  │
  │  The method accepting dims requires at least Julia 1.9.

  Examples
  ≡≡≡≡≡≡≡≡

  julia> v = [3, 1, 2]; p = zeros(Int, 3);

  julia> sortperm!(p, v); p
  3-element Vector{Int64}:
   2
   3
   1

  julia> v[p]
  3-element Vector{Int64}:
   1
   2
   3

  julia> A = [8 7; 5 6]; p = zeros(Int,2, 2);

  julia> sortperm!(p, A; dims=1); p
  2×2 Matrix{Int64}:
   2  4
   1  3

  julia> sortperm!(p, A; dims=2); p
  2×2 Matrix{Int64}:
   3  1
   2  4

With no mention of scratch-space.

I believe that any ! function is assumed to be non-allocating, and that this goes against this.

I went to check help for sort! too and did not find any mention of scratch space either.

### Tasks

Metadata

Metadata

Assignees

No one assigned

    Labels

    docsThis change adds or pertains to documentationsortingPut things in order

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions