-
Notifications
You must be signed in to change notification settings - Fork 424
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
Provide a way for sets to zipper with more things #26595
base: main
Are you sure you want to change the base?
Conversation
Adds a pair of leader/follower iterators to ChapelHashtable and a helper method, and calls them from the Set `these` iterators. The implementation does frequent recomputation of how to divide up the iteration space to find full slots. This can probably be improved on, but I wanted to take a snapshot of what worked before attempting it. It also does not help when zippering with arrays yet. That is also a next step ---- Signed-off-by: Lydia Duncan <[email protected]>
Test comes from issue chapel-lang#15824 and checks zippering two sets of the same length (but different contents). Sorts the output so that it is consistent instead of potentially racy. ---- Signed-off-by: Lydia Duncan <[email protected]>
---- Signed-off-by: Lydia Duncan <[email protected]>
This is a first step towards being able to zip sets with other types instead of just other sets ---- Signed-off-by: Lydia Duncan <[email protected]>
This meant always computing the number of chunks and which chunk we were ourselves, since the array iterator expects the other components of the tuple to be ranges as well (so we can't use followThis to send that information) and the argument list for the follower is supposed to match the argument list of the leader aside from followThis. This is unfortunate, but given the sizes I checked, seemed to be okay. ---- Signed-off-by: Lydia Duncan <[email protected]>
Covers: - converting the set to an array and then zippering - zippering with the array leading - zippering with an array when the set leads With the prior commit, all three work (though toArray already worked prior to this effort). ---- Signed-off-by: Lydia Duncan <[email protected]>
The original is only failing on one of the two machines I checked, so I'm curious if this also fails there or if it behaves as expected (it passes on the machine where the original still works) ---- Signed-off-by: Lydia Duncan <[email protected]>
---- Signed-off-by: Lydia Duncan <[email protected]>
I'm not sure if this will actually allow us to check for unequal lengths, but try it and see ---- Signed-off-by: Lydia Duncan <[email protected]>
This argument indicates the size of the leader in the iteration, so that we can validate if the iteration space is uneven. It has a default value so that the non-zippered case is not as annoying to write Update various tests to use this iterator instead ---- Signed-off-by: Lydia Duncan <[email protected]>
- document new iterator - remove commented out writelns - restore old behavior to Set's these iterators, since we're using a different iterator - comment an if statement in _determineEvenChunks - close a parenthetical in an older comment in ChapelHashtable ---- Signed-off-by: Lydia Duncan <[email protected]>
It'll present a cleaner interface to users ---- Signed-off-by: Lydia Duncan <[email protected]>
Covers: - Zippering with a shorter array as leader - Zippering with a shorter array as follower - Serial version of the new iterator - Standalone version of the new iterator ---- Signed-off-by: Lydia Duncan <[email protected]>
---- Signed-off-by: Lydia Duncan <[email protected]>
…f it Would have added a warning to all the overloads, except for the bug reported in all the right contexts, whether the bug or the unstable warning gets resolved first. ---- Signed-off-by: Lydia Duncan <[email protected]>
Use a halt wrapper instead of `__primitive("chpl_error"`, especially because this is a new error instead of trying to match errors that the compiler can insert. While doing that, made the error messages clearer about what was wrong and what should be done instead (and hopefully head off confusion for the follower case). And add a test locking in the error message in the leader case ---- Signed-off-by: Lydia Duncan <[email protected]>
---- Signed-off-by: Lydia Duncan <[email protected]>
Capturing my thoughts after seeing a demo of this today:
Some argued that since a set yields its elements arbitrarily, sets shouldn't even support zippering. I see that point (and started with that mindset myself), but think that the presence of #15824 suggests that people may want that anyway, and it also supports things like copying a set into an array when you don't care about ordering (or haven't until wanting to put it into the array). |
Interestingly, it looks as though ranges follow a different way of distributing uneven chunks than I was expecting, and this can result in the current implementation missing values from the set. For a range of 10 elements with 6 tasks, the chunks will be |
Could you summarize the follower's approach using pseudocode? |
I think I need to limit the scope back down to only "sets can zip with sets". I did encounter the same issue with arrays of that same size/number of tasks and the attempts to solve it so far have been running into walls. While I can use the range leader to get the chunk divisions when in the set/hashtable leader, trying to use it during the follower to determine how to divide up the slots gives a different answer (I'm pretty sure because when we're in the follower, the iterator understandably thinks there are a different number of tasks available to do parallel iteration). I'm not sure how to solve that without sending more information between the leader and the follower. Additionally, while I was trying to do that, I encountered several issues when trying to deal with arrays of ranges. I'm going to write them up separately, but with scaling down the scope, they won't be as relevant |
Here's the word problem description of it, will write up better pseudocode once I de-ice my driveway // determine the number of elements to return from the followThis argument (size of the range) // extrapolate from that number the number of expected chunks (basically, solve for // then go through and determine the range of slots needed to find each chunk of elements to return. The set up when I made this PR basically assumed a known threshold point where the number of elements in a chunk would change (the idea for this was based on the parIters primer). So for 10 elements and 6 tasks, we know that 6 divides 10 once, with a remainder of 4, so four tasks would take 2 elements and the remaining 2 would take 1 element. // So iterate over the slots in the hashtable, storing the point at which each threshold would be matched (2, 4, 6, 8, 9), except for the last one which is known to be "end of the table". Front-loading the tasks that take more to the beginning allowed us to mathematically compute when to stop looking for a second element - once we have filled "remainder" number of slots, we will look for the lower number of elements. // Finishing that iteration gives us the upper bound for each task's range of elements in an array, except for the last task (which again, will be the end of the table). E.g., for a hashtable with 25 slots, this could look like // To determine which of those ranges this task should actually use, go back to our guess at the chunk based on the lower bound for the element number to return. If we are task 3 (when the tasks are 0 based), for instance, the followThis would be 6..7, the chunk size is 2, and 6 divided by 2 is 3, so we have determined which task we are and will return all elements in (Edit: note that once we get to actual tasks 4 and 5, the chunk size would be 1. But since our starting location was expected to be mathematically computable and have a specific threshold where it switches, this won't pose a problem. A chunk size of 1 will lead to every element having their own subtask, and our starting element number would be |
This is the part that needs better pseudocode.
We now have all the start and end points for tasks to find each element evenly. They are |
This reverts commit 09a4bca. Partial change, but seemed like the natural way to do that part ---- Signed-off-by: Lydia Duncan <[email protected]>
…roperly ---- Signed-off-by: Lydia Duncan <[email protected]>
- Replace uses of the now-defunct contents iterator with the these iterator - Drop the tests that were explicitly only for contents (serial, standalone, and the error message test on the argument) - Futurize the tests of zippering with arrays - Add a future for zippering with a longer array - Add two futures of zippering with ranges Ranges and arrays don't work with the restored strategy (but they didn't always work with the previous one, as demonstrated by the range and longer array tests) ---- Signed-off-by: Lydia Duncan <[email protected]>
withRange1's behavior is inconsistent ---- Signed-off-by: Lydia Duncan <[email protected]>
---- Signed-off-by: Lydia Duncan <[email protected]>
---- Signed-off-by: Lydia Duncan <[email protected]>
To avoid making any sort of assumptions about what the leader is passing us / what we're zippering with, I'd suggest going for simplicity and correctness over performance in a first implementation for the set follower iterator. For example, if I'm asked to follow For more scalable approaches, my mind goes to things like having some sort of directory structure within the set / hash table that (lazily?) maintains some sort of rough sense of where items live, for example, through a computed-on-demand map, a scan operation, or the like. This problem also seems very equivalent to the kind of problem Jeremiah was wrestling with in the ParallelIO module: I want to read a file in parallel, but don't know what offset task # i of n should start at since the "rows" of a file (say) aren't all of equal size. I can't recall what approach he took there, and felt similarly like it was very challenging to parallelize, but remember being convinced that what he ended up with was sound (I think? Though it worries me that I can't remember it). But again, I'd prefer to have it working correctly and flexibly first, and then worry about performance in a follow-on effort. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The code as-written looks good to me, but I'm inclined to agree with Brad about focusing on simplicity and correctness.
Hmm, to be fair, that's both simpler and probably better performance than what I had implemented. I'll give it a shot and see where it gets me, thanks! |
Brad pointed out that we could just count through the filled elements of the hashtable until we reach the count specified by the `low` value, and then start yielding. This is more performant than what I had been doing and it allows more flexibility, enabling it to actually work with ranges and arrays The one drawback is that it does mean we don't detect when the leader is shorter than the follower, but that's already true for arrays so we'll at least be consistent with them. ---- Signed-off-by: Lydia Duncan <[email protected]>
- Updates the expected behavior for setZipDifferentSize.chpl and arrayShorter.chpl - Removes the .bad and .future files for the tests that now work - Adds a .prediff I missed for the second range test because the test wasn't working before - Updates the expected output for withArrayLonger1.chpl, again because the test wasn't working before ---- Signed-off-by: Lydia Duncan <[email protected]>
Okay, it looks like that does result in us failing to detect when the leader is shorter than the follower, but we already said that was okay because it was consistent with what arrays do. Running a paratest now, but otherwise I think this is ready for another look (sorry Ben XD) |
Prior to this work, sets could only zipper with identical other sets (or basically just itself). Zippering with sets of the same length, or with arrays, would result in errors about unequal zippering lengths.
Resolves #15824
Adds a leader/follower pair of iterators to ChapelHashtable to support this effort. These iterators and their support functions allow the hashtable to evenly divide the elements stored in it, instead of evenly dividing the entire hashtable's storage capacity.
While here, I fixed a comment that was missing a closing parenthesis.
New tests:
toArray
result (user code, but not expected to have changed as a result of this effort)Places for improvement:
Passed a full paratest with futures