Skip to content

Instantly share code, notes, and snippets.

@andyferris
Last active February 24, 2017 01:15
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save andyferris/67c5c163160efeea20a9ada49fdb6fba to your computer and use it in GitHub Desktop.
Save andyferris/67c5c163160efeea20a9ada49fdb6fba to your computer and use it in GitHub Desktop.
Comparison of direct generated functions vs. `unroll_tuple` as a utility function to unroll expressions
# "bespoke" generated functions
Pkg.checkout("StaticArrays","master")
using StaticArrays
f(v) = @inbounds return v[v]
v = SVector{3,Int}((1,2,3))
# results:
julia> @code_native f(v)
.text
Filename: REPL[3]
pushq %rbp
movq %rsp, %rbp
Source line: 25
movq (%rsi), %rax
movq 8(%rsi), %rcx
movq -8(%rsi,%rax,8), %rax
movq -8(%rsi,%rcx,8), %rcx
movq 16(%rsi), %rdx
movq -8(%rsi,%rdx,8), %rdx
Source line: 1
movq %rax, (%rdi)
movq %rcx, 8(%rdi)
movq %rdx, 16(%rdi)
movq %rdi, %rax
popq %rbp
retq
nop
julia> @code_warntype f(v)
Variables:
#self#::#f
v::StaticArrays.SVector{3,Int64}
Body:
begin
$(Expr(:inbounds, true))
# meta: location /home/ferris/.julia/v0.6/StaticArrays/src/indexing.jl getindex 20
# meta: location /home/ferris/.julia/v0.6/StaticArrays/src/indexing.jl # line 25:
SSAValue(0) = $(Expr(:new, :($(QuoteNode(StaticArrays.SVector{3,Int64}))), :((Core.tuple)((Base.getfield)((Core.getfield)(v, :data)::Tuple{Int64,Int64,Int64}, (Base.getfield)((Core.getfield)(v, :data)::Tuple{Int64,Int64,Int64}, 1)::Int64)::Int64, (Base.getfield)((Core.getfield)(v, :data)::Tuple{Int64,Int64,Int64}, (Base.getfield)((Core.getfield)(v, :data)::Tuple{Int64,Int64,Int64}, 2)::Int64)::Int64, (Base.getfield)((Core.getfield)(v, :data)::Tuple{Int64,Int64,Int64}, (Base.getfield)((Core.getfield)(v, :data)::Tuple{Int64,Int64,Int64}, 3)::Int64)::Int64)::Tuple{Int64,Int64,Int64})))
# meta: pop location
# meta: pop location
return SSAValue(0)
$(Expr(:inbounds, :pop))
end::StaticArrays.SVector{3,Int64}
# "centralized" generated functions
Pkg.checkout("StaticArrays","julia-0.6")
using StaticArrays
f(v) = @inbounds return v[v]
v = SVector{3,Int}((1,2,3))
# results:
julia> @code_native f(v)
.text
Filename: REPL[3]
pushq %rbp
movq %rsp, %rbp
Source line: 7
movq 16(%rsi), %rax
movq %rax, -80(%rbp)
vmovups (%rsi), %xmm0
vmovaps %xmm0, -96(%rbp)
movq 16(%rsi), %rax
movq %rax, -56(%rbp)
vmovups (%rsi), %xmm0
vmovups %xmm0, -72(%rbp)
vmovups -96(%rbp), %ymm0
vmovups -80(%rbp), %ymm1
vmovups %ymm1, -32(%rbp)
vmovups %ymm0, -48(%rbp)
Source line: 13
movq -24(%rbp), %rax
movq -16(%rbp), %rcx
movq -56(%rbp,%rax,8), %rax
movq -56(%rbp,%rcx,8), %rcx
movq -8(%rbp), %rdx
movq -56(%rbp,%rdx,8), %rdx
Source line: 1
movq %rax, (%rdi)
movq %rcx, 8(%rdi)
movq %rdx, 16(%rdi)
movq %rdi, %rax
popq %rbp
vzeroupper
retq
nopl (%rax,%rax)
julia> @code_warntype f(v)
Variables:
#self#::#f
v::StaticArrays.SVector{3,Int64}
#151::StaticArrays.##151#152{StaticArrays.SVector{3,Int64},StaticArrays.SVector{3,Int64}}
Body:
begin
$(Expr(:inbounds, true))
# meta: location /home/ferris/.julia/v0.6/StaticArrays/src/indexing.jl getindex 7
SSAValue(0) = $(QuoteNode(StaticArrays.SVector{3,Int64}))
#151::StaticArrays.##151#152{StaticArrays.SVector{3,Int64},StaticArrays.SVector{3,Int64}} = $(Expr(:new, :($(QuoteNode(StaticArrays.##151#152{StaticArrays.SVector{3,Int64},StaticArrays.SVector{3,Int64}}))), :(v), :(v)))
SSAValue(1) = #151::StaticArrays.##151#152{StaticArrays.SVector{3,Int64},StaticArrays.SVector{3,Int64}}
SSAValue(2) = $(QuoteNode(Length(3)))
# meta: location /home/ferris/.julia/v0.6/StaticArrays/src/traits.jl unroll_tuple 105
# meta: location /home/ferris/.julia/v0.6/StaticArrays/src/generated.jl unroll_tuple 9
# meta: location /home/ferris/.julia/v0.6/StaticArrays/src/generated.jl # line 13:
SSAValue(4) = (Core.tuple)((Base.getfield)((Core.getfield)((Core.getfield)(SSAValue(1), :a)::StaticArrays.SVector{3,Int64}, :data)::Tuple{Int64,Int64,Int64}, (Base.getfield)((Core.getfield)((Core.getfield)(SSAValue(1), :inds)::StaticArrays.SVector{3,Int64}, :data)::Tuple{Int64,Int64,Int64}, 1)::Int64)::Int64, (Base.getfield)((Core.getfield)((Core.getfield)(SSAValue(1), :a)::StaticArrays.SVector{3,Int64}, :data)::Tuple{Int64,Int64,Int64}, (Base.getfield)((Core.getfield)((Core.getfield)(SSAValue(1), :inds)::StaticArrays.SVector{3,Int64}, :data)::Tuple{Int64,Int64,Int64}, 2)::Int64)::Int64, (Base.getfield)((Core.getfield)((Core.getfield)(SSAValue(1), :a)::StaticArrays.SVector{3,Int64}, :data)::Tuple{Int64,Int64,Int64}, (Base.getfield)((Core.getfield)((Core.getfield)(SSAValue(1), :inds)::StaticArrays.SVector{3,Int64}, :data)::Tuple{Int64,Int64,Int64}, 3)::Int64)::Int64)::Tuple{Int64,Int64,Int64}
# meta: pop location
# meta: pop location
# meta: pop location
SSAValue(3) = SSAValue(4)
# meta: pop location
return $(Expr(:new, :($(QuoteNode(StaticArrays.SVector{3,Int64}))), SSAValue(3)))
$(Expr(:inbounds, :pop))
end::StaticArrays.SVector{3,Int64}
@andyferris
Copy link
Author

The new methods in question are this getindex and unroll_tuple.

There seems to be enough inbounds propagation, so it's nice to see that working.

@andyferris
Copy link
Author

andyferris commented Feb 24, 2017

For reference, this is the "thunk-based" system that does generate good code:

@propagate_inbounds function getindex(a::AbstractArray, inds::StaticVector{<:Integer})
    _getindex(Size(inds), a, inds)
end

@generated function _getindex(s::Size{S}, a::AbstractArray, inds::StaticVector{<:Integer}) where S
    @assert(S isa Tuple{Int})
    exprs = [:(a[inds[$i]]) for i = 1:S[1]]
    return quote
        @_propagate_inbounds_meta
        return similar_type(a, eltype(a), s)($(Expr(:tuple, exprs...)))
    end
end

This just takes what is on master and moves the Size computation to a thunk and moves similar_type to the generated body.

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