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

RFC: making tuples easy and fun #15516

Merged
merged 1 commit into from
Jan 24, 2017
Merged

RFC: making tuples easy and fun #15516

merged 1 commit into from
Jan 24, 2017

Conversation

JeffBezanson
Copy link
Member

This allows constructing a tuple by type, from an iterator. Suddenly they seem a lot friendlier:

julia> NTuple{5,UInt8}("Hello")
(0x48,0x65,0x6c,0x6c,0x6f)

julia> Tuple{Int,Float32,Int,Float32}(repeated(0))
(0,0.0,0,0.0)

julia> NTuple{100,Int8}(countfrom(2))
(2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101)

For better or for worse, the code is fully unrolled, so when using NTuple the size of the generated code is exponential in the length of your input.

I kind of like the permissiveness of taking a prefix of the iterator, instead of requiring the whole thing to be consumed (there is an error for that commented out in this patch). Otherwise you have to write e.g. NTuple{5,T}(repeated(0, 5)), and unfortunately adding in the Take iterator leads to much worse code generation.

This technique could also be used to implement a very efficient Partition iterator.

@toivoh
Copy link
Contributor

toivoh commented Mar 15, 2016

Nice!
Really exponential? Linear in the length of the tuple sounds reasonable.

@JeffBezanson
Copy link
Member Author

Yes, it's linear in the length of the tuple but NTuple lets you type in the length as a number.


_t(::Type{Tuple{}}, itr, s) = () # done(itr,s) ? () : error("too many values")

function _t(T, itr, s)
Copy link
Contributor

Choose a reason for hiding this comment

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

I realize it's internal, but I'm sure there's a better name for this than _t

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes I'll change it in the final version.

@vtjnash
Copy link
Member

vtjnash commented Mar 15, 2016

can this be made constant in the length of the tuple? once you pass the unrolling threshold (4-8?), i expect that would execute faster on a modern cpu.

@JeffBezanson
Copy link
Member Author

I would love to do that, but I'm not sure how. Any ideas?

@eschnett
Copy link
Contributor

"exponential in the length of the input" for an input size of 100 would mean a code size of 2^100... what machine do you have?

@JeffBezanson
Copy link
Member Author

Yes that would happen if you passed a 100 digit number to NTuple. I was just being cute.

@vtjnash
Copy link
Member

vtjnash commented Mar 15, 2016

I would love to do that, but I'm not sure how. Any ideas?

hm, perhaps spitballing here, but this executes the above NTuple example with surprising ease:

function (::Type{NTuple{N, T}}){N,T}(itr)
    s = start(itr)
    return (T[begin (i,s)=next(itr,s);i; end for x in 1:N]...)::NTuple{N,T}
end

julia> @time NTuple{100,Int8}(countfrom(2))
  0.028234 seconds (18.92 k allocations: 893.138 KB)

julia> @time NTuple{100,Int8}(countfrom(2))
  0.000023 seconds (109 allocations: 3.031 KB)
# the allocations here are from poor codegen for jl_f_tuple, and are not inherent to the representation

julia> @time NTuple{99,Int8}(countfrom(2))
  0.029041 seconds (11.51 k allocations: 546.124 KB)
# ^ there doesn't be a penalty here, since method specialization on N does not improve the generated code

vs. the PR code:

julia> @time NTuple{100,Int8}(countfrom(2))
  3.029220 seconds (5.61 M allocations: 226.754 MB, 3.15% gc time)

julia> @time NTuple{100,Int8}(countfrom(2))
  0.000014 seconds (7 allocations: 1.172 KB)
# ^ a bit faster, but codegen improvements can easily fix that

julia> @time NTuple{99,Int8}(countfrom(2))
  0.115941 seconds (120.72 k allocations: 4.519 MB, 7.05% gc time)
# ^ oops, that's a high recurring cost

edit: forgot to paste the timing for the first codegen timing of my code

@JeffBezanson
Copy link
Member Author

I was hoping to avoid the intermediate array, but yes I guess for now we can include this implementation. I guess the thing to do is to add your NTuple method, with a switch to call the code in this PR for N less than some cutoff. Arguably (though somewhat counter-intuitively) the temporary array is more of a deal-breaker for small data than for large.

@vtjnash
Copy link
Member

vtjnash commented Mar 15, 2016

If we could stack allocate the intermediate object, I believe that would allow codegen to do the unrolling itself? So we just need to make codegen smarter. For now, I think adding a manual cutoff makes sense.

@StefanKarpinski
Copy link
Member

bump?

@JeffBezanson
Copy link
Member Author

Rebased and updated!

@vchuravy vchuravy added this to the 0.6.0 milestone Jan 24, 2017
@timholy
Copy link
Member

timholy commented Jan 24, 2017

While on the subject of tuples, does anyone else think that we might want to put some tuple-manipulation functions on more "official" footing? I think about half of my packages these days start with using Base: tail, front. Base.IteratorsMD.split is also dang useful.

@StefanKarpinski
Copy link
Member

Failure in test/replutil.jl.

@JeffBezanson JeffBezanson merged commit 51f96b0 into master Jan 24, 2017
@tkelman tkelman deleted the jb/moretuples branch January 24, 2017 23:52
@quinnj
Copy link
Member

quinnj commented Jan 24, 2017

I was hoping this would make it in!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

9 participants