-
-
Notifications
You must be signed in to change notification settings - Fork 5.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Export oneto rather than implement range(stop) #39242
Conversation
Can you explain the need for this? I don't think #39223 is compelling. There's a downside to creating confusion about whether people should use |
Executive Summary
ExpositionIn Julia 1.7, Julia will likely have a Upon considering two and three positional argument The syntax The Julia community has grown significantly and is now large enough to perhaps permit some differences in coding style. Given that Upon coming across |
The primary reason-for-being for In my view, if the possibility of returning a That said, it really surprises/distresses me how many packages are reaching for Base.OneTo to define a loop's iteration space. 😕 |
I'd expect it to make a performance difference when the range gets passed around, e.g. that julia> a = rand(100);
julia> @btime sum(view($a, 1:59))
27.865 ns (0 allocations: 0 bytes)
29.085241951206672
julia> @btime sum(view($a, Base.OneTo(59)))
27.558 ns (0 allocations: 0 bytes)
29.085241951206672
julia> @btime sum(view($a, 1:59))
27.873 ns (0 allocations: 0 bytes)
29.085241951206672
julia> @btime sum(view($a, Base.OneTo(59)))
27.528 ns (0 allocations: 0 bytes)
29.085241951206672 That's easily within the margin of noise. So I wouldn't expect much difference in typical use. But whenever you aren't passing it around, and instead writing something like
I didn't know it was that unidiomatic. I'll stick to |
That's an interesting example. On 1.5 I see a 2x (simd) speedup for OneTo but can't see the difference in the LLVM (or anywhere LLVM is making use of the hardcoded 1 beyond the stack-allocated struct size, for that matter. Compare "Distresses" is far too strong, but it sure feels funny to reach for an unexported and specialized type that's 10 more characters when there's no functional difference (looking at |
Yeah, I'm bothered by that too. Probably those of us who were around when we added |
Co-authored-by: Daniel Karrasch <[email protected]>
We have not formally declared what is public vs private API: #7561 #35715 . Both issues remain open. Regarding However, On the other hand, it is used in examples in the Base docs such as for To clarify, I'm not suggesting we export I am suggesting we export |
oneto
rather than implement range(stop)
I stared at that for a little while, but couldn't find the difference. 12c12
< add rsi, qword ptr [r14 + 24]
---
> add rsi, qword ptr [r14 + 16]
41c41
< mov r8, qword ptr [r14 + 24]
---
> mov r8, qword ptr [r14 + 16] I'd have to look at the high level Julia code to see if maybe it's being taken care of outside an inline boundary. function mysum(a)
s = zero(eltype(a))
@inbounds @simd for i ∈ eachindex(a)
s += a[i]
end
s
end
code_llvm(mysum, Tuple{SubArray{Float64,1,Array{Float64,1},Tuple{UnitRange{Int64}},true}}, debuginfo=:none)
code_llvm(mysum, Tuple{SubArray{Float64,1,Array{Float64,1},Tuple{Base.OneTo{Int64}},true}}, debuginfo=:none)
code_native(mysum, Tuple{SubArray{Float64,1,Array{Float64,1},Tuple{UnitRange{Int64}},true}}, debuginfo=:none, syntax=:intel)
code_native(mysum, Tuple{SubArray{Float64,1,Array{Float64,1},Tuple{Base.OneTo{Int64}},true}}, debuginfo=:none, syntax=:intel) From the top:
%1 = getelementptr inbounds { {}*, [1 x [2 x i64]], i64, i64 }, { {}*, [1 x [2 x i64]], i64, i64 }* %0, i64 0, i32 1, i64 0, i64 1
%2 = getelementptr inbounds { {}*, [1 x [2 x i64]], i64, i64 }, { {}*, [1 x [2 x i64]], i64, i64 }* %0, i64 0, i32 1, i64 0, i64 0
%3 = load i64, i64* %1, align 8
%4 = load i64, i64* %2, align 8
%5 = sub i64 %3, %4
%6 = add i64 %5, 1
%1 = getelementptr inbounds { {}*, [1 x [1 x i64]], i64, i64 }, { {}*, [1 x [1 x i64]], i64, i64 }* %0, i64 0, i32 1, i64 0, i64 0
%2 = load i64, i64* %1, align 8 A load, sub, and add were eliminated. The difference in assembly matches, mov rax, qword ptr [rdi + 16]
sub rax, qword ptr [rdi + 8]
inc rax
test rax, rax vs mov rax, qword ptr [rdi + 8]
test rax, rax
As for performance: julia> x = rand(100);
julia> @btime mysum(view($x, 1:32))
3.332 ns (0 allocations: 0 bytes)
17.408491415901285
julia> @btime mysum(view($x, Base.OneTo(32)))
4.314 ns (0 allocations: 0 bytes)
17.408491415901285
julia> @btime mysum(view($x, 1:64))
5.310 ns (0 allocations: 0 bytes)
33.568214211566435
julia> @btime mysum(view($x, Base.OneTo(64)))
6.375 ns (0 allocations: 0 bytes)
33.568214211566435
julia> @btime mysum(view($x, 1:96))
7.091 ns (0 allocations: 0 bytes)
47.45531942465689
julia> @btime mysum(view($x, Base.OneTo(96)))
8.600 ns (0 allocations: 0 bytes)
47.45531942465689 Identical performance could be explained by inlining, but better? julia> @noinline function mysum_noinline(a)
s = zero(eltype(a))
@inbounds @simd for i ∈ eachindex(a)
s += a[i]
end
s
end
mysum_noinline (generic function with 1 method)
julia> @btime mysum_noinline(view($x, 1:32))
9.908 ns (0 allocations: 0 bytes)
17.408491415901285
julia> @btime mysum_noinline(view($x, Base.OneTo(32)))
10.554 ns (0 allocations: 0 bytes)
17.408491415901285
julia> @btime mysum_noinline(view($x, 1:64))
10.761 ns (0 allocations: 0 bytes)
33.568214211566435
julia> @btime mysum_noinline(view($x, Base.OneTo(64)))
12.027 ns (0 allocations: 0 bytes)
33.568214211566435
julia> @btime mysum_noinline(view($x, 1:96))
11.709 ns (0 allocations: 0 bytes)
47.45531942465689
julia> @btime mysum_noinline(view($x, Base.OneTo(96)))
13.136 ns (0 allocations: 0 bytes)
47.45531942465689 FWIW, I restarted Julia and the difference reversed. julia> @btime mysum_noinline(view($x, 1:32))
11.762 ns (0 allocations: 0 bytes)
16.72168408851738
julia> @btime mysum_noinline(view($x, Base.OneTo(32)))
8.926 ns (0 allocations: 0 bytes)
16.72168408851738
julia> @btime mysum_noinline(view($x, 1:64))
11.040 ns (0 allocations: 0 bytes)
31.75307213919455
julia> @btime mysum_noinline(view($x, Base.OneTo(64)))
10.593 ns (0 allocations: 0 bytes)
31.75307213919455
julia> @btime mysum_noinline(view($x, 1:96))
12.046 ns (0 allocations: 0 bytes)
45.69956955548978
julia> @btime mysum_noinline(view($x, Base.OneTo(96)))
11.742 ns (0 allocations: 0 bytes)
45.69956955548978 But these are just artifacts. 1 ns is much bigger than the theoretical difference we'd expect. But it should be obvious that 2 instructions outside of a loop should make a tiny difference. A CPU running at 4GHz has 4 clock cycles per julia> using LinuxPerf#master
julia> foreachf(f::F, N, args::Vararg{<:Any,A}) where {F,A} = foreach(_ -> f(args...), Base.OneTo(N))
julia> @pstats "cpu-cycles,(instructions,branch-instructions,branch-misses),(task-clock,context-switches,cpu-migrations,page-faults),(L1-dcache-load-misses,L1-dcache-loads,L1-icache-load-misses),(dTLB-load-misses,dTLB-loads),(iTLB-load-misses,iTLB-loads)" begin
foreachf(mysum_noinline, 10_000_000, view(x, Base.OneTo(96)))
end
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
╶ cpu-cycles 2.48e+09 60.0% # 4.3 cycles per ns
┌ instructions 7.02e+09 60.1% # 2.8 insns per cycle
│ branch-instructions 1.27e+09 60.1% # 18.1% of instructions
└ branch-misses 2.28e+06 60.1% # 0.2% of branch instructions
┌ task-clock 5.81e+08 100.0% # 580.8 ms
│ context-switches 0.00e+00 100.0%
│ cpu-migrations 0.00e+00 100.0%
└ page-faults 2.40e+01 100.0%
┌ L1-dcache-load-misses 1.65e+07 20.0% # 0.7% of dcache loads
│ L1-dcache-loads 2.24e+09 20.0%
└ L1-icache-load-misses 5.32e+06 20.0%
┌ dTLB-load-misses 2.40e+05 20.0% # 0.0% of dTLB loads
└ dTLB-loads 2.25e+09 20.0%
┌ iTLB-load-misses 2.46e+05 39.9% # 87.6% of iTLB loads
└ iTLB-loads 2.81e+05 39.9%
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ With 2.8 instructions per cycle and 4.3 cycles per nanosecond, it would be extremely difficult to actually measure the (tiny) expected difference in performance of cutting out 2 instructions out of the many instructions it takes to run the loop. Any any other artifacts, at least some of which apply consistent (and far larger) biases throughout a Julia session (or at least many
The reason I'd been using it for |
The least controversial road to One positional argument is more challenging and controversial since you have to route from range(start; stop=nothing, length::Union{Integer,Nothing}=nothing, step=nothing) =
_range_positional(start, step, stop, length)
...
range(stop::Integer) = range_stop(stop)
_range_positional(stop::Any , step::Nothing, ::Nothing, len::Nothing) =
_range(nothing, nothing, stop, nothing) # One arg interpreted as `stop`, could be nothing
_range_positional(start::Any , step::Any , stop::Any, len::Any) =
_range(start, step, stop, len)
...
range_stop(stop) = oneunit(stop):stop
range_stop(stop::Integer) = OneTo(stop) julia> methods(range)
# 3 methods for generic function "range":
[1] range(stop::Integer) in Main at REPL[296]:1
[2] range(start; stop, length, step) in Main at REPL[295]:1
[3] range(start, stop; length, step) in Main at REPL[156]:1
julia> range(stop) = range_stop(stop) # Simpler implementation, but messier method table below
range (generic function with 3 methods)
julia> methods(range)
# 3 methods for generic function "range":
[1] range(stop::Integer) in Main at REPL[296]:1
[2] range(stop; stop, length, step) in Main at REPL[302]:1 # That looks messy
[3] range(start, stop; length, step) in Main at REPL[156]:1 I'll consider resubmitting a one positional argument |
Seeing implementation like `Base.OneTo` in error messages may be confusing to some users (cf discussion in JuliaLang#39242, [discourse](https://discourse.julialang.org/t/promote-shape-dimension-mismatch/57529/)). This PR turns ```julia julia> ones(2, 3) + ones(3, 2) ERROR: DimensionMismatch("dimensions must match: a has dims (Base.OneTo(2), Base.OneTo(3)), b has dims (Base.OneTo(3), Base.OneTo(2)), mismatch at 1") ``` into ```julia julia> ones(2, 3) + ones(3, 2) ERROR: DimensionMismatch("dimensions must match: a has axes (1:2, 1:3), b has axes (1:3, 1:2), mismatch at 1") ``` Fixes JuliaLang#40118. Acked-by: Tamas K. Papp <[email protected]>
Seeing implementation like `Base.OneTo` in error messages may be confusing to some users (cf discussion in JuliaLang#39242, [discourse](https://discourse.julialang.org/t/promote-shape-dimension-mismatch/57529/)). This PR turns ```julia julia> ones(2, 3) + ones(3, 2) ERROR: DimensionMismatch("dimensions must match: a has dims (Base.OneTo(2), Base.OneTo(3)), b has dims (Base.OneTo(3), Base.OneTo(2)), mismatch at 1") ``` into ```julia julia> ones(2, 3) + ones(3, 2) ERROR: DimensionMismatch("dimensions must match: a has axes (1:2, 1:3), b has axes (1:3, 1:2), mismatch at 1") ``` Fixes JuliaLang#40118. Acked-by: Tamas K. Papp <[email protected]>
Seeing implementation like `Base.OneTo` in error messages may be confusing to some users (cf discussion in JuliaLang#39242, [discourse](https://discourse.julialang.org/t/promote-shape-dimension-mismatch/57529/)). This PR turns ```julia julia> ones(2, 3) + ones(3, 2) ERROR: DimensionMismatch("dimensions must match: a has dims (Base.OneTo(2), Base.OneTo(3)), b has dims (Base.OneTo(3), Base.OneTo(2)), mismatch at 1") ``` into ```julia julia> ones(2, 3) + ones(3, 2) ERROR: DimensionMismatch("dimensions must match: a has axes (1:2, 1:3), b has axes (1:3, 1:2), mismatch at 1") ``` Fixes JuliaLang#40118. Acked-by: Tamas K. Papp <[email protected]>
Seeing implementation like `Base.OneTo` in error messages may be confusing to some users (cf discussion in JuliaLang#39242, [discourse](https://discourse.julialang.org/t/promote-shape-dimension-mismatch/57529/)). This PR turns ```julia julia> ones(2, 3) + ones(3, 2) ERROR: DimensionMismatch("dimensions must match: a has dims (Base.OneTo(2), Base.OneTo(3)), b has dims (Base.OneTo(3), Base.OneTo(2)), mismatch at 1") ``` into ```julia julia> ones(2, 3) + ones(3, 2) ERROR: DimensionMismatch("dimensions must match: a has axes (1:2, 1:3), b has axes (1:3, 1:2), mismatch at 1") ``` Fixes JuliaLang#40118. Acked-by: Tamas K. Papp <[email protected]>
Seeing implementation details like `Base.OneTo` in error messages may be confusing to some users (cf discussion in #39242, [discourse](https://discourse.julialang.org/t/promote-shape-dimension-mismatch/57529/)). This PR turns ```julia julia> ones(2, 3) + ones(3, 2) ERROR: DimensionMismatch("dimensions must match: a has dims (Base.OneTo(2), Base.OneTo(3)), b has dims (Base.OneTo(3), Base.OneTo(2)), mismatch at 1") ``` into ```julia julia> ones(2, 3) + ones(3, 2) ERROR: DimensionMismatch("dimensions must match: a has size (2, 3), b has size (3, 2), mismatch at 1") ``` Fixes #40118. (This is basically #40124, but redone because I made a mess rebasing). --------- Co-authored-by: Jameson Nash <[email protected]>
Export
oneto
as an alternative torange(stop)
oneto(n)
is an unambiguous alternative torange(stop)
oneto(n)
may be more intuitive than1:n
for some people from distinct coding traditions (e.g. Python)oneto(n)
uses Julia's type system to allow for fast code (viaBase.OneTo
)Since a single argument
range
mapping toBase.OneTo
is controversial (see #39223), let's export the recently createdoneto
(all lower case) which is unambiguous and accepts a singleInteger
argument.oneto
was recently merged intoBase
via #37741.oneto
is five lowercase letters likerange
but it clearly describes what it does. Additionally, as demonstrated by #37741 it can be extended for uses not directly involvingBase.OneTo
.By exporting
oneto
we will make a highly optimized code path easily available for a very common use case.