Skip to content

recognize Tuple and AbstractVector as Ordered in Base.OrderStyle - #62485

Merged
LilithHafner merged 3 commits into
JuliaLang:masterfrom
AMVS24:unique_fix_61959
Jul 26, 2026
Merged

recognize Tuple and AbstractVector as Ordered in Base.OrderStyle#62485
LilithHafner merged 3 commits into
JuliaLang:masterfrom
AMVS24:unique_fix_61959

Conversation

@AMVS24

@AMVS24 AMVS24 commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

As mentioned in issue #61959 unique! and allunique have a sorted fast path method gated on Base.OrderStyle, but the trait only applies to a short, obvious list(Real, AbstractString, Symbol and Union{}), leaving the fast path unreachable for any other valid case. Since Tuples and Vectors both famously have a total ordering which satisfies the contiguous requirement of unique! they should be included

Added:

  • OrderStyle(::Type{<:AbstractVector{T}}) : vector is Ordered iff T is
  • _tuple_ordering() : Recursively checks that every field type is Ordered
julia> b = @be sort!([(rand(1:20), rand(1:20)) for _ in 1:100]) unique!
Benchmark: 4523 samples with 6 evaluations
 min    1.100 μs (11 allocs: 5.981 KiB)
 median 1.400 μs (11 allocs: 5.999 KiB)
 mean   2.611 μs (11 allocs: 5.999 KiB, 0.19% gc time)
 max    538.167 μs (11 allocs: 6.015 KiB, 99.52% gc time)
 julia> b = @be sort!([(rand(1:20), rand(1:20)) for _ in 1:100]) unique!
Benchmark: 2390 samples with 337 evaluations
 min    76.558 ns
 median 88.131 ns
 mean   88.775 ns
 max    359.644 ns

The original issue also mentioned CartesianIndex and other user defined types that simply wrap tuples/vectors but I'd like maintainer input before tackling that

@AMVS24
AMVS24 force-pushed the unique_fix_61959 branch from 3ed3bc5 to d7eb148 Compare July 23, 2026 14:27
@adienes adienes added performance Must go faster collections Data structures holding multiple items, e.g. sets sorting Put things in order status: waiting for PR reviewer labels Jul 23, 2026
Comment thread base/traits.jl Outdated
OrderStyle(::Type{<:Any}) = Unordered()
OrderStyle(::Type{Union{}}, slurp...) = Ordered()

OrderStyle(T::Type{<:Tuple}) = _tuple_ordering(T)

@adienes adienes Jul 23, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
OrderStyle(T::Type{<:Tuple}) = _tuple_ordering(T)
OrderStyle(T::Type{<:Tuple}) = all(==(Ordered()) OrderStyle, fieldtypes(T)) ? Ordered() : Unordered()

@AMVS24 AMVS24 Jul 23, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure why but all() reports consistently higher runtimes than recursion.

julia> b = @be sort!([(rand(1:20), rand(1:20)) for _ in 1:100]) unique!
Benchmark: 2811 samples with 185 evaluations
 min    150.270 ns
 median 163.243 ns
 mean   164.669 ns
 max    362.703 ns
julia> b = @be sort!([(rand(1:20), rand(1:20)) for _ in 1:1000]) unique!
Benchmark: 1453 samples with 68 evaluations
 min    436.765 ns
 median 461.765 ns
 mean   464.643 ns
 max    1.015 μs
Benchmark: 3049 samples with 316 evaluations
 min    71.835 ns
 median 87.658 ns
 mean   88.143 ns
 max    305.696 ns
julia> b = @be sort!([(rand(1:20), rand(1:20)) for _ in 1:1000]) unique!
Benchmark: 1470 samples with 86 evaluations
 min    365.116 ns
 median 391.860 ns
 mean   393.518 ns
 max    852.326 ns

I'd guess that all() isn't able to resolve at compile time and is adding runtime overhead

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe needs a check for isconcretetype

julia> function OrderStyle(::Type{T}) where {T<:Tuple}
           isconcretetype(T) || return Unordered()
           all(S -> OrderStyle(S) === Ordered(), fieldtypes(T)) ? Ordered() : Unordered()
       end
OrderStyle

julia> b = @be sort!([(rand(1:20), rand(1:20)) for _ in 1:100]) unique!
Benchmark: 3540 samples with 148 evaluations
 min    148.649 ns
 median 163.851 ns
 mean   167.909 ns

this is also needed for correctness. the current implementation fails on something like unique!([(1,), (1,2)]) since fieldtypes doesn't work

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@adienes Pushed the isconcretetype() check, also found out adding map() skips iterating over at runtime so speeds are the same as recursion without the O(N^2) complexity

julia> @be sort!([(rand(1:20), rand(1:20)) for _ in 1:100]) unique!
Benchmark: 2510 samples with 323 evaluations
 min    78.328 ns
 median 88.545 ns
 mean   89.448 ns
 max    583.591 ns

Comment thread base/traits.jl Outdated
Co-authored-by: Andy Dienes <51664769+adienes@users.noreply.github.com>
@LilithHafner
LilithHafner merged commit 07da71a into JuliaLang:master Jul 26, 2026
10 checks passed
@LilithHafner

Copy link
Copy Markdown
Member

Thanks, @AMVS24!

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

Labels

collections Data structures holding multiple items, e.g. sets performance Must go faster sorting Put things in order

Projects

None yet

Development

Successfully merging this pull request may close these issues.

unique! misses sorted fast-path for vectors of Tuple, CartesianIndex, user-defined types, etc

3 participants