Skip to content
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

Improve inferability of slicedim #20154

Merged
merged 4 commits into from
Jan 31, 2017
Merged

Improve inferability of slicedim #20154

merged 4 commits into from
Jan 31, 2017

Conversation

martinholters
Copy link
Member

@martinholters martinholters commented Jan 20, 2017

An attempt to fix #20141. Not perfect, but an improvement, and should do no harm.

E.g. with this PR:

julia> @code_warntype slicedim(ones(3), 1, 1)
# ...
  end::Union{Array{Float64,1},Float64}

julia> @code_warntype slicedim(ones(3), 1, 1:1)
# ...
  end::Array{Float64,1}

julia> @code_warntype slicedim(ones(3,3), 1, 1)
# ...
  end::Union{Array{Float64,1},Array{Float64,2},Float64}

julia> @code_warntype slicedim(ones(3,3), 1, 1:1)
# ...
  end::Array{Float64,2}

julia> @code_warntype slicedim(ones(3,3,3), 1, 1)
# ...
  end::Any

julia> @code_warntype slicedim(ones(3,3,3), 1, 1:1)
# ...
  end::Array{Float64,3}

julia> @code_warntype slicedim(ones(3,3,3,3,3,3), 1, 1:1)
# ...
  end::Array{Float64,6}

julia> @code_warntype slicedim(ones(3,3,3,3,3,3,3), 1, 1:1)
# ...
  end::Any

On master, they are all just ::Any.

@@ -91,6 +91,10 @@ imag{T<:Real}(x::AbstractArray{T}) = zero(x)

# index A[:,:,...,i,:,:,...] where "i" is in dimension "d"

_slice_inds(new_inds::Tuple, inds::Tuple, d, i) =
_slice_inds((new_inds..., length(new_inds) + 1 == d ? i : first(inds)), tail(inds), d, i)
_slice_inds(new_inds::Tuple, inds::Tuple{}, d, i) = new_inds
Copy link
Member Author

Choose a reason for hiding this comment

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

Could also be

setindex(x::Tuple, n, v) = _setindex((), x, n, v)
_setindex(y::Tuple, x::Tuple, n, v) = _setindex((y..., length(y) + 1 == n ? v : first(x)), tail(x), n, v)
_setindex(y::Tuple, x::Tuple{}, n, v) = y

and live in tuple.jl. Or do we have something like this somewhere already?

Copy link
Sponsor Member

Choose a reason for hiding this comment

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

Seems like a good idea. You'd want the v to come before n in the argument order, though, for consistency with setindex!.

@kshyatt kshyatt added compiler:inference Type inference domain:arrays [a, r, r, a, y, s] labels Jan 20, 2017
Copy link
Sponsor Member

@timholy timholy left a comment

Choose a reason for hiding this comment

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

Maybe briefer would be ntuple(j->ifelse(j==d,i,inds[d]), Val{N}), but overall I like the idea of having an internal tuple-setindex, it makes it more readable.

I'd also point out that you may be able to get full inferrability of the output in most cases by manually allocating the output array and then using copy!. As long as the indices-tuple is homogenous, the indices-tuple of the output array differs by being of length N-1 rather than N. This is the same type as tail would produce, so you could define

drop_dth{N,I}(inds::NTuple{N,I}, d) = _drop_dth(tail(inds), (), d, inds...)
_drop_dth(ref, out, d, ind, inds...) = length(out)==d+1 ? _drop_dth(ref, out, d, inds...) : _drop_dth(ref, (out..., ind), d, inds...)
_drop_dth{M,I}(::NTuple{M,I}, out, d)::NTuple{M,I} = out  # M = N-1

drop_dth is type-unstable at intermediate stages, but the output tuple has known type. Pass out as the indices-tuple to similar, and voila, the return array is inferrable.

(Might need a fallback for non-homogeneous indices tuples, but AFAIK there are no such arrays in existence.)

@@ -91,6 +91,10 @@ imag{T<:Real}(x::AbstractArray{T}) = zero(x)

# index A[:,:,...,i,:,:,...] where "i" is in dimension "d"

_slice_inds(new_inds::Tuple, inds::Tuple, d, i) =
_slice_inds((new_inds..., length(new_inds) + 1 == d ? i : first(inds)), tail(inds), d, i)
Copy link
Sponsor Member

@timholy timholy Jan 20, 2017

Choose a reason for hiding this comment

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

I'd considerevaluate ifelse here since ? will give you one branch per dimension. Might also consider @inlineing this.

@@ -91,6 +91,10 @@ imag{T<:Real}(x::AbstractArray{T}) = zero(x)

# index A[:,:,...,i,:,:,...] where "i" is in dimension "d"

_slice_inds(new_inds::Tuple, inds::Tuple, d, i) =
_slice_inds((new_inds..., length(new_inds) + 1 == d ? i : first(inds)), tail(inds), d, i)
_slice_inds(new_inds::Tuple, inds::Tuple{}, d, i) = new_inds
Copy link
Sponsor Member

Choose a reason for hiding this comment

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

Seems like a good idea. You'd want the v to come before n in the argument order, though, for consistency with setindex!.

@martinholters
Copy link
Member Author

the indices-tuple of the output array differs by being of length N-1 rather than N

We support d>N, so the length of the output array indices tuple depends on the value of d.

@timholy
Copy link
Sponsor Member

timholy commented Jan 21, 2017

Seems almost worth having a separate code path for this case, e.g., require an extra argument or something. But that's not pretty, so maybe it's better to live with the type-instability.

@martinholters
Copy link
Member Author

Introduced setindex(x::Tuple, v, i) instead of _slice_inds and updated slicedim accordingly.

@timholy Does ntuple work for you the way you suggested? For the (f, Val{N}) case, it seems it requires a type-stable f?

@timholy
Copy link
Sponsor Member

timholy commented Jan 23, 2017

Hmm, seems likely. This version looks fine to me.

@martinholters
Copy link
Member Author

Should setindex and/or _setindex be @inline? You probably have more experience whether/when this is beneficial.

@@ -18,6 +18,12 @@ getindex(t::Tuple, i::Real) = getfield(t, convert(Int, i))
getindex{T}(t::Tuple, r::AbstractArray{T,1}) = tuple([t[ri] for ri in r]...)
getindex(t::Tuple, b::AbstractArray{Bool,1}) = length(b) == length(t) ? getindex(t,find(b)) : throw(BoundsError(t, b))

# returns new tuple; N.B.: becomes no-op if i is out-of-bounds
setindex(x::Tuple, v, i::Integer) = _setindex((), x, v, i::Integer)
_setindex(y::Tuple, r::Tuple, v, i::Integer) =
Copy link
Sponsor Member

Choose a reason for hiding this comment

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

This is the one to inline.

@martinholters
Copy link
Member Author

Added @_inline_meta and a test.

@martinholters martinholters changed the title RFC: Improve inferability of slicedim Improve inferability of slicedim Jan 23, 2017
@martinholters
Copy link
Member Author

Oh, there is also a slicedim specialization for BitArray that could use a similar treatment. Will take a closer look tomorrow.

@martinholters
Copy link
Member Author

Interesting, the BitArray specialization seems to be significantly slower (orders of magnitude!) than the generic code. Hence I have just removed it. Could someone double-check whether my benchmark in this gist makes sense? (Note: I've run it against this branch, so by default there is no specialization.)

The specialized code also did not allow a "too large" dimension as long as indexing with 1, so could be made type-stable. But for consistency, probably it should either always be allowed or always disallowed.

@martinholters
Copy link
Member Author

Travis failure on Mac looks unrelated.

@martinholters
Copy link
Member Author

martinholters commented Jan 25, 2017

Good news:

Doing some more benchmarking I am now pretty convinced that the generic code is significantly faster than the specialized code for BitArray, so removing it seems the right thing to do, except...

Bad news:

#20233

If we want to preserve the current behavior, I could add back a specialization for BitArray{1}. (Edit: done.)

@martinholters
Copy link
Member Author

Added a specialization so that this PR only does what the title says and does not change any return types.

B[l] = A[j + index_offset]
l += 1
end
# preserve some special behavior
Copy link
Contributor

Choose a reason for hiding this comment

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

was this tested very carefully before?

Copy link
Member Author

Choose a reason for hiding this comment

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

No, it's not tested at all. And I didn't feel like adding a test now because the desired behavior might actually be redefined shortly depending on how we resolve #20233.

@StefanKarpinski
Copy link
Sponsor Member

Bump – this seems like a strict improvement.

@martinholters
Copy link
Member Author

Barring any objections, I'm going to merge this tomorrow.

@martinholters martinholters merged commit 552d5e0 into master Jan 31, 2017
@martinholters martinholters deleted the mh/slicedim branch January 31, 2017 10:29
@mbauman mbauman mentioned this pull request Jul 30, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
compiler:inference Type inference domain:arrays [a, r, r, a, y, s]
Projects
None yet
Development

Successfully merging this pull request may close these issues.

slicedim is type-unstable
5 participants