Skip to content

Commit

Permalink
Make DefaultStable and DefaultUnstable dispatchable and display witho…
Browse files Browse the repository at this point in the history
…ut internals (#56661)

Previously, `DEFAULT_STABLE` was a giant chain of algorithms reflecting
the full sorting dispatch system. Now, it's simply `DefaultStable()`.
This has a few minor impacts:

Previously, the public binding `Base.Sort.DEFAULT_STABLE` documented
non-public implementation details of sorting dispatch in its extended
help with a caviat that they are internal. Now,
`Base.Sort.DEFAULT_STABLE` refers to the non-public binding
`Base.Sort.DefaultStable` and implementation details are documented
there with a warning that they are non-public.

Previously, dispatching on `Base.Sort.DEFAULT_STABLE` required writing
`::typeof(Base.Sort.DEFAULT_STABLE)` whereas now one could alternatively
dispatch on the (internal) type `Base.Sort.DefaultStable`.

Previously `Base.Sort.DEFAULT_STABLE === Base.Sort.DEFAULT_UNSTABLE` so
when writing sorting algorithms for custom collections it was impossible
to determine if the user asked for a stable algorithm. Now
`DEFAULT_STABLE` is `DefaultStable()` and `DEFAULT_UNSTABLE` is
`DefaultUnstable()`. Both the algorithms expand to the same large chain
of algorithms `_DEFAULT_ALGORITHMS_FOR_VECTORS` but it is possible to
intercept them before that happens.

`Base.Sort.DEFAULT_STABLE` now prints as `DefaultStable()` instead of

```julia-repl
julia> Base.Sort.DEFAULT_STABLE
Base.Sort.SubArrayOptimization(
    Base.Sort.MissingOptimization(
        Base.Sort.BoolOptimization(
            Base.Sort.Small{10}(
                Base.Sort.InsertionSortAlg(),
                Base.Sort.IEEEFloatOptimization(
                    Base.Sort.IsUIntMappable(
                        Base.Sort.Small{40}(
                            Base.Sort.InsertionSortAlg(),
                            Base.Sort.CheckSorted(
                                Base.Sort.ComputeExtrema(
                                    Base.Sort.ConsiderCountingSort(
                                        Base.Sort.CountingSort(),
                                        Base.Sort.ConsiderRadixSort(
                                            Base.Sort.RadixSort(),
                                            Base.Sort.Small{80}(
                                                Base.Sort.InsertionSortAlg(),
                                                Base.Sort.ScratchQuickSort(missing, missing,
                                                    Base.Sort.InsertionSortAlg()))))))),
                        Base.Sort.StableCheckSorted(
                            Base.Sort.ScratchQuickSort(missing, missing,
                                Base.Sort.InsertionSortAlg()))))))))
```

Factored out of #54494 at Triage's request (the git history reflects
this history).

---------

Co-authored-by: Lars Göttgens <[email protected]>
  • Loading branch information
LilithHafner and lgoettgens authored Nov 26, 2024
1 parent ea82538 commit a17db2b
Show file tree
Hide file tree
Showing 2 changed files with 50 additions and 26 deletions.
61 changes: 41 additions & 20 deletions base/sort.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1475,21 +1475,15 @@ InitialOptimizations(next) = SubArrayOptimization(
Small{10}(
IEEEFloatOptimization(
next)))))
"""
DEFAULT_STABLE

The default sorting algorithm.
This algorithm is guaranteed to be stable (i.e. it will not reorder elements that compare
equal). It makes an effort to be fast for most inputs.
The algorithms used by `DEFAULT_STABLE` are an implementation detail. See extended help
for the current dispatch system.
"""
struct DefaultStable <: Algorithm end
# Extended Help
`DefaultStable` is an algorithm which indicates that a fast, general purpose sorting
algorithm should be used, but does not specify exactly which algorithm.
`DEFAULT_STABLE` is composed of two parts: the [`InitialOptimizations`](@ref) and a hybrid
of Radix, Insertion, Counting, Quick sorts.
Currently, it is composed of two parts: the [`InitialOptimizations`](@ref) and a hybrid of
Radix, Insertion, Counting, Quick sorts.
We begin with MissingOptimization because it has no runtime cost when it is not
triggered and can enable other optimizations to be applied later. For example,
Expand Down Expand Up @@ -1549,7 +1543,39 @@ stage.
Finally, if the input has length less than 80, we dispatch to [`InsertionSort`](@ref) and
otherwise we dispatch to [`ScratchQuickSort`](@ref).
"""
const DEFAULT_STABLE = InitialOptimizations(
struct DefaultStable <: Algorithm end

"""
DEFAULT_STABLE
The default sorting algorithm.
This algorithm is guaranteed to be stable (i.e. it will not reorder elements that compare
equal). It makes an effort to be fast for most inputs.
The algorithms used by `DEFAULT_STABLE` are an implementation detail. See the docstring
of `Base.Sort.DefaultStable` for the current dispatch system.
"""
const DEFAULT_STABLE = DefaultStable()

"""
DefaultUnstable <: Algorithm
Like [`DefaultStable`](@ref), but does not guarantee stability.
"""
struct DefaultUnstable <: Algorithm end

"""
DEFAULT_UNSTABLE
An efficient sorting algorithm which may or may not be stable.
The algorithms used by `DEFAULT_UNSTABLE` are an implementation detail. They are currently
the same as those used by [`DEFAULT_STABLE`](@ref), but this is subject to change in future.
"""
const DEFAULT_UNSTABLE = DefaultUnstable()

const _DEFAULT_ALGORITHMS_FOR_VECTORS = InitialOptimizations(
IsUIntMappable(
Small{40}(
CheckSorted(
Expand All @@ -1560,15 +1586,10 @@ const DEFAULT_STABLE = InitialOptimizations(
ScratchQuickSort())))))),
StableCheckSorted(
ScratchQuickSort())))
"""
DEFAULT_UNSTABLE

An efficient sorting algorithm.
_sort!(v::AbstractVector, ::Union{DefaultStable, DefaultUnstable}, o::Ordering, kw) =
_sort!(v, _DEFAULT_ALGORITHMS_FOR_VECTORS, o, kw)

The algorithms used by `DEFAULT_UNSTABLE` are an implementation detail. They are currently
the same as those used by [`DEFAULT_STABLE`](@ref), but this is subject to change in future.
"""
const DEFAULT_UNSTABLE = DEFAULT_STABLE
const SMALL_THRESHOLD = 20

function Base.show(io::IO, alg::Algorithm)
Expand Down
15 changes: 9 additions & 6 deletions test/sorting.jl
Original file line number Diff line number Diff line change
Expand Up @@ -819,9 +819,9 @@ end
let
requires_uint_mappable = Union{Base.Sort.RadixSort, Base.Sort.ConsiderRadixSort,
Base.Sort.CountingSort, Base.Sort.ConsiderCountingSort,
typeof(Base.Sort.DEFAULT_STABLE.next.next.next.big.next.yes),
typeof(Base.Sort.DEFAULT_STABLE.next.next.next.big.next.yes.big),
typeof(Base.Sort.DEFAULT_STABLE.next.next.next.big.next.yes.big.next)}
typeof(Base.Sort._DEFAULT_ALGORITHMS_FOR_VECTORS.next.next.next.big.next.yes),
typeof(Base.Sort._DEFAULT_ALGORITHMS_FOR_VECTORS.next.next.next.big.next.yes.big),
typeof(Base.Sort._DEFAULT_ALGORITHMS_FOR_VECTORS.next.next.next.big.next.yes.big.next)}

function test_alg(kw, alg, float=true)
for order in [Base.Forward, Base.Reverse, Base.By(x -> x^2)]
Expand Down Expand Up @@ -861,15 +861,18 @@ end
end
end

test_alg_rec(Base.DEFAULT_STABLE)
test_alg_rec(Base.Sort._DEFAULT_ALGORITHMS_FOR_VECTORS)
end
end

@testset "show(::Algorithm)" begin
@test eval(Meta.parse(string(Base.DEFAULT_STABLE))) === Base.DEFAULT_STABLE
lines = split(string(Base.DEFAULT_STABLE), '\n')
@test eval(Meta.parse(string(Base.Sort._DEFAULT_ALGORITHMS_FOR_VECTORS))) === Base.Sort._DEFAULT_ALGORITHMS_FOR_VECTORS
lines = split(string(Base.Sort._DEFAULT_ALGORITHMS_FOR_VECTORS), '\n')
@test 10 < maximum(length, lines) < 100
@test 1 < length(lines) < 30

@test eval(Meta.parse(string(Base.DEFAULT_STABLE))) === Base.DEFAULT_STABLE
@test string(Base.DEFAULT_STABLE) == "Base.Sort.DefaultStable()"
end

@testset "Extensibility" begin
Expand Down

0 comments on commit a17db2b

Please sign in to comment.