-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Added Block Size Checks for ParquetLoader #120
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
Changes from 3 commits
49bb968
64de2d6
6e4d46c
9967d12
ec1e78a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -94,7 +94,8 @@ public sealed class Arguments | |
| private readonly int _columnChunkReadSize; | ||
| private readonly Column[] _columnsLoaded; | ||
| private readonly DataSet _schemaDataSet; | ||
| private const int _defaultColumnChunkReadSize = 100; // Should ideally be close to Rowgroup size | ||
| private const int _defaultColumnChunkReadSize = 1000000; | ||
| private const string _chunkSizeShortName = "chunkSize"; | ||
|
|
||
| private bool _disposed; | ||
|
|
||
|
|
@@ -368,8 +369,8 @@ private sealed class Cursor : RootCursorBase, IRowCursor | |
| private readonly Delegate[] _getters; | ||
| private readonly ReaderOptions _readerOptions; | ||
| private int _curDataSetRow; | ||
| private IEnumerator _dataSetEnumerator; | ||
| private IEnumerator _blockEnumerator; | ||
| private IEnumerator<int> _dataSetEnumerator; | ||
| private IEnumerator<int> _blockEnumerator; | ||
| private IList[] _columnValues; | ||
| private IRandom _rand; | ||
|
|
||
|
|
@@ -390,11 +391,32 @@ public Cursor(ParquetLoader parent, Func<int, bool> predicate, IRandom rand) | |
| Columns = _loader._columnsLoaded.Select(i => i.Name).ToArray() | ||
| }; | ||
|
|
||
| int numBlocks = (int)Math.Ceiling(((decimal)parent.GetRowCount() / _readerOptions.Count)); | ||
| int[] blockOrder = _rand == null ? Utils.GetIdentityPermutation(numBlocks) : Utils.GetRandomPermutation(rand, numBlocks); | ||
| _blockEnumerator = blockOrder.GetEnumerator(); | ||
| // The number of blocks is calculated based on the specified rows in a block (defaults to 1M). | ||
| // Since we want to shuffle the blocks in addition to shuffling the rows in each block, checks | ||
| // are put in place to ensure we can produce a shuffle order for the blocks. | ||
| int numBlocks; | ||
| int[] blockOrder; | ||
| try | ||
| { | ||
| numBlocks = checked((int)Math.Ceiling(((decimal)parent.GetRowCount() / _readerOptions.Count))); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
why cast long to decimal? why not just stick to long? #Closed
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Because we want the That said: I don't particularly like this cast to In reply to: 187491573 [](ancestors = 187491573)
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. do we really expect the number of rows to be above 2^^63? That seem like a very large number, we are talking Exobytes.. In reply to: 187502257 [](ancestors = 187502257,187491573)
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Well, merely casing to long and doing a normal division won't save you. The issue isn't 2^^63 or any other large number. The issue is that divisions over integers are truncating divisions, which is the opposite of what we want sometimes like now when we're trying to figure out how many containers we'll need of capacity Let's consider, row count Now consider if In reply to: 187506601 [](ancestors = 187506601,187502257,187491573)
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Originally the decimal cast was because that's the only way to come out of the division with a decimal point instead of the closest lesser integer, but I've added a method in MathUtils to do the calculation with longs for clarity and to minimize casting instead.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
consider using numblocks as long and then just use an if statement if it is above int.max
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, exception driven computation is kind of one of those mechanisms best left as a last resort. Storing in long and then using a simple comparison is better for sure. In reply to: 187492091 [](ancestors = 187492091)
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed! |
||
| } | ||
| catch (OverflowException) | ||
| { | ||
| // this exception is thrown when number of blocks exceeds int.MaxValue | ||
| throw _loader._host.ExceptParam("ColumnChunkReadSize", "Error due to too many blocks. Try increasing block size."); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
nameof( #Closed
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed, along with other places better off with nameof(). |
||
| } | ||
| try | ||
| { | ||
| blockOrder = _rand == null ? Utils.GetIdentityPermutation(numBlocks) : Utils.GetRandomPermutation(rand, numBlocks); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Now here all you need is to get a random ordering of integers from 0 to N. why do you need to allocate an array, cant you just write a function to return an IEnumerable that would return random sequence of blocks without having to allocate this.? Now, if you use the IEnumerable that generates a random sequence, it can even use Longs for number of blocks. #Resolved
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This might be a research question of whether the "randomness" from this (what is this, an LCG?) is sufficient. Right now our permutation is generated using a pretty good PRNG generator -- albeit one that uses I might choose to delay for this reason: Elsewhere in our codebase we don't insist that a random order without repetitions be accomplished while using no additional memory (as evidenced by the fact that we have a utility for a materialized array, vs. having a utility like you mention, and we've used the first ). So: is there a strong reason why we would insist on it here, especially since there are valid questions in the linked discussion about whether this simple modular arithmetic scheme is random enough for many applications? In reply to: 187491929 [](ancestors = 187491929)
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Consider having a shuffling scheme based on In reply to: 187505463 [](ancestors = 187505463,187491929)
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To me the last question that Tom raised directs how to move forward. Since I don't have any real insight into what kind of Parquet files will be loaded, I can't really say how powerful the shuffle should be, or if what we have now is sufficient for most cases (maybe someone with more experience could shed some light,) though it seems for a single Parquet file, 300M rows*300M blocks covers a wide range, and can also be expanded if the dataset were saved as multiple Parquet files. For now I've kept the array shuffle unless the elements don't fit in an array, in which case the data is read sequentially up to the size of int.MaxValue. If we decide to go forward with BigArray or another means of shuffling, I will look into it at a later time.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. My guess the implementation there just allocates an array and that throws. We should avoid it. Can we at least do some kind of hierarchical randomness where we don't need to allocate the entire array of blocks? In reply to: 187505300 [](ancestors = 187505300)
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hi Gleb, this comment was on the sequential case. There's certainly no need to allocate there. Did you mean this comment to go on the above thread? Let's talk about it here though just so we don't break threads. Right, so, these questions we're having are, I think, a pretty clear sign of something goes well beyond the scope of what this PR is meant to do. Regarding the linked stack overflow discussion, I think using a PRNG scheme with like LCG, which has a bad reputation verging on infamy, or any close variant, is not something we ought to just accept reflexively. If you feel strongly about it, perhaps an issue should be filed, so whether or not that level of shuffling is sufficient or produces good practical results can be evaluated. In the meantime, is using the existing mechanism that is used elsewhere without incident sufficient, until that hypothetical investigation is finished? In reply to: 187506292 [](ancestors = 187506292,187505300)
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thank for the clarification Tom. I agree that this discussion, although good to have, is beyond the scope of this PR. The original implementation was done to conform to the pattern of the other shuffling mechanisms of this library. If there is an agreement on the implementation we're happy to follow guidance. At this moment however, I'm seconding the vote for array shuffling until a sufficient MathUtils replacement is created that supports IEnumerator. Please advise. |
||
| } | ||
| catch (OutOfMemoryException) | ||
| { | ||
| // This exception is thrown when attempting to create an array of more than ~300M elements | ||
| throw _loader._host.ExceptParam(_chunkSizeShortName, "Error due to too many blocks. Try increasing block size."); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
In the unlikely event that there is a file with more blocks than can fit in an array in .NET, neither should we throw. When some circumstances prevents us from serving up a random cursor in any other implementation of
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Certainly we don't expect our cursor creation anywhere else to throw on valid inputs except in exceptional circumstances like, say, an input file has been deleted. In reply to: 187505473 [](ancestors = 187505473)
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Right, the IEnumerable fix for the sequential case takes away the restriction of the number of elements needing to fit in an array, so one would expect the "next best thing" to a shuffled sequence to be an unshuffled sequence, instead of just throwing. For now ParquetLoader will give a sequential cursor when it's unable to produce a shuffled sequence. |
||
| } | ||
| _blockEnumerator = blockOrder.Cast<int>().GetEnumerator(); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Please just declare
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. |
||
|
|
||
| _dataSetEnumerator = new int[0].GetEnumerator(); // Initialize an empty enumerator to get started | ||
| _dataSetEnumerator = new int[0].Cast<int>().GetEnumerator(); // Initialize an empty enumerator to get started | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There's no need to allocate even an empty array. You may just use |
||
| _columnValues = new IList[_actives.Length]; | ||
| _getters = new Delegate[_actives.Length]; | ||
| for (int i = 0; i < _actives.Length; ++i) | ||
|
|
@@ -472,12 +494,12 @@ protected override bool MoveNextCore() | |
| { | ||
| if (_dataSetEnumerator.MoveNext()) | ||
| { | ||
| _curDataSetRow = (int)_dataSetEnumerator.Current; | ||
| _curDataSetRow = _dataSetEnumerator.Current; | ||
| return true; | ||
| } | ||
| else if (_blockEnumerator.MoveNext()) | ||
| { | ||
| _readerOptions.Offset = (int)_blockEnumerator.Current * _readerOptions.Count; | ||
| _readerOptions.Offset = (long)_blockEnumerator.Current * _readerOptions.Count; | ||
|
|
||
| // When current dataset runs out, read the next portion of the parquet file. | ||
| DataSet ds; | ||
|
|
@@ -486,8 +508,17 @@ protected override bool MoveNextCore() | |
| ds = ParquetReader.Read(_loader._parquetStream, _loader._parquetOptions, _readerOptions); | ||
| } | ||
|
|
||
| int[] dataSetOrder = _rand == null ? Utils.GetIdentityPermutation(ds.RowCount) : Utils.GetRandomPermutation(_rand, ds.RowCount); | ||
| _dataSetEnumerator = dataSetOrder.GetEnumerator(); | ||
| int[] dataSetOrder; | ||
| try | ||
| { | ||
| dataSetOrder = _rand == null ? Utils.GetIdentityPermutation(ds.RowCount) : Utils.GetRandomPermutation(_rand, ds.RowCount); | ||
| } | ||
| catch (OutOfMemoryException) | ||
| { | ||
| // This exception will be thrown when trying to create an array that is too big. | ||
| throw _loader._host.ExceptParam(_chunkSizeShortName, "Error caused because block size is too big. Try decreasing block size."); | ||
| } | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This seems identical code with line 402. Can this be factored out? #Closed
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. |
||
| _dataSetEnumerator = dataSetOrder.Cast<int>().GetEnumerator(); | ||
| _curDataSetRow = dataSetOrder[0]; | ||
|
|
||
| // Cache list for each active column | ||
|
|
@@ -671,4 +702,4 @@ private string ConvertListToString(IList list) | |
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.
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.
Hi, I guess the idea here is we want error messages to use the short name, but this is inconsistent with other things in the codebase that just use the field name. Also the short name is relevant only to command line users, but is not relevant to ML.NET's API users.) Remove this constand string, and replace all usages with
nameof(Arguments.ColumnChunkReadSize). #ClosedUh oh!
There was an error while loading. Please reload this page.
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.
TIL of nameof(). Fixed! #Closed