Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions base/exports.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1384,6 +1384,7 @@ export
@label,
@goto,
@view,
@views,

# SparseArrays module re-exports
SparseArrays,
Expand Down
3 changes: 2 additions & 1 deletion base/subarray.jl
Original file line number Diff line number Diff line change
Expand Up @@ -381,7 +381,8 @@ end

Creates a `SubArray` from an indexing expression. This can only be applied directly to a
reference expression (e.g. `@view A[1,2:end]`), and should *not* be used as the target of
an assignment (e.g. `@view(A[1,2:end]) = ...`).
an assignment (e.g. `@view(A[1,2:end]) = ...`). See also [`@views`](@ref)
to switch an entire block of code to use views for slicing.
"""
macro view(ex)
if isa(ex, Expr) && ex.head == :ref
Expand Down
63 changes: 63 additions & 0 deletions base/util.jl
Original file line number Diff line number Diff line change
Expand Up @@ -750,3 +750,66 @@ kwdef_val(::Type{Cwstring}) = Cwstring(C_NULL)
kwdef_val{T<:Integer}(::Type{T}) = zero(T)

kwdef_val{T}(::Type{T}) = T()

############################################################################
# @views macro (not defined in subarray.jl because of a bootstrapping
# issue with the code generation below).

# maybeview is like getindex, but returns a view for slicing operations
# (while remaining equivalent to getindex for scalar indices and non-array types)
@propagate_inbounds maybeview(A, args...) = getindex(A, args...)
@propagate_inbounds maybeview(A::AbstractArray, args...) = view(A, args...)
@propagate_inbounds maybeview(A::AbstractArray, args::Number...) = getindex(A, args...)
@propagate_inbounds maybeview(A) = getindex(A)
@propagate_inbounds maybeview(A::AbstractArray) = getindex(A)
# avoid splatting penalty in common cases:
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.

Was this really necessary in your testing? I'd be surprised if the @propagate_inbounds definitions above have a splatting penalty since they get inlined.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I tried a simple test case

f(x) = 1
f(x,y) = 2
g(x...) = f(x...)

and it wasn't getting inlined, but I guess I should try again with @propagate_inbounds?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Hmm, I think I was getting deceived: @code_llvm g(3) looks more complicated than @code_llvm f(3), but foo() = g(3) + g(4) - g(6,7) + g(8) is definitely inlining g (and simplifies to ret i64 1).

Great, that will simply things. The dotview function in broadcast.jl can be similarly simplified.

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.

Yeah, @code_llvm can be deceiving with splatted @inline functions because it shows the LLVM function for them... but that's not at all what happens when they get inlined into another function. I almost always will define simple fixed-argument wrapper functions when I'm trying to test these guys.

let pi(expr) = :(@propagate_inbounds $expr)
for nargs = 1:5
args = Symbol[Symbol("x",i) for i = 1:nargs]
numargs = Expr[:($(Symbol("x",i))::Number) for i = 1:nargs]
eval(pi(Expr(:(=), Expr(:call, :maybeview, :A, args...),
Expr(:block, Expr(:call, :getindex, :A, args...)))))
eval(pi(Expr(:(=), Expr(:call, :maybeview, :(A::AbstractArray), args...),
Expr(:block, Expr(:call, :view, :A, args...)))))
eval(pi(Expr(:(=), Expr(:call, :maybeview, :(A::AbstractArray), numargs...),
Expr(:block, Expr(:call, :getindex, :A, args...)))))
end
end

_views(x) = x
_views(x::Symbol) = esc(x)
function _views(ex::Expr)
if ex.head in (:(=), :(.=))
# don't use view on the lhs of an assignment
Expr(ex.head, esc(ex.args[1]), _views(ex.args[2]))
elseif ex.head == :ref
Expr(:call, :maybeview, map(_views, ex.args)...)
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.

replace_ref_end!?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Good catch, thanks.

else
h = string(ex.head)
if last(h) == '='
# don't use view on the lhs of an op-assignment
Expr(first(h) == '.' ? :(.=) : :(=), esc(ex.args[1]),
Expr(:call, esc(Symbol(h[1:end-1])), _views(ex.args[1]),
map(_views, ex.args[2:end])...))
else
Expr(ex.head, map(_views, ex.args)...)
end
end
end

"""
@views code

Convert every array-slicing operation in the given `code`
(which may be a `begin`/`end` block, loop, function, etc.)
to return a view. Scalar indices, non-array types, and
explicit `getindex` calls (as opposed to `array[...]`) are
unaffected.

Note that the `@views` macro only affects `array[...]` expressions
that appear explicitly in the given code, not array slicing that
occurs in functions called by the code.
"""
macro views(x)
_views(x)
end
1 change: 1 addition & 0 deletions doc/src/stdlib/arrays.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ Base.Broadcast.broadcast!
Base.getindex(::AbstractArray, ::Any...)
Base.view
Base.@view
Base.@views
Base.to_indices
Base.Colon
Base.parent
Expand Down