chapel-lang / chapel

a Productive Parallel Programming Language
https://chapel-lang.org
Other
1.76k stars 414 forks source link

Introduce a constant value for an empty range or domain #8907

Open LouisJenkinsCS opened 6 years ago

LouisJenkinsCS commented 6 years ago

I believe that there is a need for a constant for empty ranges; currently a way to declare an empty range is something along the lines of 0..-1, but this looks rather non-intuitive to newcomers. It may not be immediately apparent as you would not generally loop over an empty range (at least not intentionally), but imagine you want to declare a field for an array that you do not know the size of yet...

// Imagine each of these records are huge and they 
// hold a lot of nested records and large # of fields
record LargeRecord {
   // ...
}
var arr : [0..0] LargeRecord;

Now the above arr allocates one of these large records which increases the time spent for allocation (if its large enough). If later you want to resize arr you have to deal with it copying the older unused LargeRecord by-value into the larger array unnecessarily. This is inefficient especially if it is a field of some class instance.

Now the current way to declare an array as empty is like such...

var arr : [0..-1] LargeRecord;

Which will eliminate the extra copy and increased allocation time, but it looks non-intuitive... what does this even mean? We have an array which starts at 0 and ends at -1? Is this undefined? Of course not, but it could be a common train of thought for a newcomer.

Perhaps we should add a new constant as part of the domain or range namespace...

var arr : [domain.empty] int; // or range.empty

I believe this makes it much clearer.

LouisJenkinsCS commented 6 years ago

To show an example in a core Chapel module that uses this empty domain syntax, see the DefaultAssociative module that complains about it and later directly uses it.

LouisJenkinsCS commented 6 years ago

Another potential use-case is when you have operations like push_back on an array with the domain {M..N}, which would result in the change of the domain to {M..N+1}. If we make domain.empty be a constant range {0..-1}, what happens when the user wants to start some other index? Perhaps there should be a inline function that takes in the start index idx and produces an empty domain {idx..idx-1}? Like this...

// {1..-0}
var arr : [domain.empty(1)] int;

where...

inline proc domain.empty(idx : integral) {
   return {idx..idx-1};
}
ronawho commented 6 years ago

I like this proposal, but note that an interesting case to keep in mind is for uints, where 0:uint - 1 is max(uint)

LouisJenkinsCS commented 6 years ago

That's a good point... I think perhaps we could modify the _aligned field in ChapelRange.chpl to be something similar to a bitmask. This way it can hold whether or not the range is 'empty' and whether it is 'aligned' in the same field and it won't change the size of the range construct.

If a domain is created by domain.empty(0:uint), then it will create a range with the flag empty set, _low = 0 and _high = max(uint). This way any arrays or distributions that are being created with this domain can check whether the domain is 'empty' and hold off on allocation.

bradcray commented 6 years ago

Addressing this discussion in backwards order:

mppf commented 6 years ago

FWIW I think 1..0 is a reasonable canonical empty range.