It might scale just fine, because LLVM might discover the pattern of your 
"giant switch statement" and essentially compute a jump location. Even if not, 
if you're calling this repeatedly with a tuple of consistent length, the 
branches become predictable and then they kind of don't count. So in most 
cases I doubt it's possible to beat the performance of the solution you 
posted.

For small tuples, you can essentially match the performance of your generated 
function using "lispy" recursion. This is more friendly for the compiler, 
which you may or may not care about. (It's something we care about in Base, 
but there's no reason you necessarily have to worry about it in your own 
code.)

First, a couple of convenience functions:

@inline push(t::Tuple, item) = (t..., item)
@inline unshift(t::Tuple, item) = (item, t...)
# we'll also need something like shift!, but that's already present
# and called Base.tail

Now let's implement your function:

drop_ith(t, i) = 1 <= i <= length(t) ?
    _drop_ith((), t, i) :
    throw(ArgumentError("need 1 <= $i <= $(length(t))"))
@inline function _drop_ith(out, t, i)
    h, t1 = t[1], Base.tail(t)
    _drop_ith(length(out)+1 == i ? unshift(out, h) : push(out, h), t1, i)
end
_drop_ith(out, ::Tuple{}, i) = Base.tail(out)

This uses the sneaky trick of preserving type-stability by putting the element 
you want to drop into the first position, thus ensuring that the tuple always 
grows by 1 on each level of recursion. (For tuples, the length is part of the 
type, so making the length predictable is critical for type-stability.) At the 
end, you strip off the first element.

This works well up to tuples of size 14; any bigger, and a variable called 
MAX_TUPLETYPE_LEN ends up kicking in and hurting performance. However, it has 
the advantage of not needing Val tricks or @generated functions. In cases 
where there's less run-time checking, this approach can usually match 
@generated solutions, so the general technique is worth knowing.

Best,
--Tim

On Friday, June 24, 2016 9:12:53 AM CDT jw3126 wrote:
> The following is faster, though it does not scale very well for large
> indices because of ridiculous if chains...
> 
> ```
> @generated function droptup{N,T,i}(x::NTuple{N,T}, ::Type{Val{i}})
>     @assert 1 <= i <= N
>     args = [:(x[$j]) for j in deleteat!([1:N...], i)]
>     Expr(:tuple, args...)
> end
> 
> @generated function droptup{N,T}(x::NTuple{N,T}, i::Int)
>     quote
>         @nif $N d->(i==d) d-> droptup(x, Val{d})
>     end
> end
> 
> using BenchmarkTools
> x = (10,20,30,40)
> 
> @benchmark droptup($x, 4)
> ```
> 
> BenchmarkTools.Trial:
>   samples:          10000
>   evals/sample:     1000
>   time tolerance:   5.00%
>   memory tolerance: 1.00%
>   memory estimate:  0.00 bytes
>   allocs estimate:  0
>   minimum time:     6.00 ns (0.00% GC)
>   median time:      6.00 ns (0.00% GC)
>   mean time:        6.11 ns (0.00% GC)
>   maximum time:     70.00 ns (0.00% GC)
> 
> On Friday, June 24, 2016 at 4:50:59 PM UTC+2, jw3126 wrote:
> > Okay thanks, it works! However it has extremely poor performance. I would
> > love to do this stack allocated.
> > 
> > ```
> > using BenchmarkTools
> > function subtuple(t::Tuple,i::Integer)
> > 
> >     idx = 1:length(t)
> >     idx = setdiff(idx,i)
> >     t[idx]
> > 
> > end
> > 
> > @benchmark subtuple($(1,2,3,4), $1)
> > ```
> > 
> > BenchmarkTools.Trial:
> >   samples:          10000
> >   evals/sample:     10
> >   time tolerance:   5.00%
> >   memory tolerance: 1.00%
> >   memory estimate:  1.33 kb
> >   allocs estimate:  22
> >   minimum time:     1.52 μs (0.00% GC)
> >   median time:      1.69 μs (0.00% GC)
> >   mean time:        1.96 μs (9.07% GC)
> >   maximum time:     323.58 μs (98.21% GC)
> > 
> > On Friday, June 24, 2016 at 4:42:17 PM UTC+2, STAR0SS wrote:
> >> You can do something like that:
> >> 
> >> t = tuple(1,2,3,4)
> >> 
> >> function subtuple(t::Tuple,i::Integer)
> >> 
> >>     idx = 1:length(t)
> >>     idx = setdiff(idx,i)
> >>     t[idx]
> >> 
> >> end
> >> 
> >> subtuple(t,3)
> >> 
> >> (1,2,4)


Reply via email to