Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 4.3k
ARROW-12597: [C++] Enable per-row-group parallelism in async Parquet reader#10482
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
Closed
Uh oh!
There was an error while loading. Please reload this page.
Closed
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
8aa0e0c
ARROW-12597: [C++] Avoid nested parallelism in Parquet reader
lidavidm 78f47ad
ARROW-12597: [C++] Enable per-batch fanout when scanning Parquet
lidavidm 90f7995
ARROW-12916: [C++] Always transfer to executor
lidavidm f6e88ea
ARROW-12597: [C++] Address review feedback
lidavidm 39fd5f5
ARROW-12597: [C++] Combine ReadRowGroups
lidavidm dc6320b
ARROW-12597: [C++] Fix MSVC warning
lidavidm File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Jump to file
Failed to load files.
Loading
Uh oh!
There was an error while loading. Please reload this page.
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -21,7 +21,9 @@ | ||
| #include <vector> | ||
| #include "arrow/status.h" | ||
| #include "arrow/util/functional.h" | ||
| #include "arrow/util/thread_pool.h" | ||
| #include "arrow/util/vector.h" | ||
| namespace arrow { | ||
| namespace internal { | ||
| @@ -44,6 +46,21 @@ Status ParallelFor(int num_tasks, FUNCTION&& func, | ||
| return st; | ||
| } | ||
| template <class FUNCTION, typename T, | ||
| typename R = typename internal::call_traits::return_type<FUNCTION>::ValueType> | ||
| Future<std::vector<R>> ParallelForAsync( | ||
| std::vector<T> inputs, FUNCTION&& func, | ||
| Executor* executor = internal::GetCpuThreadPool()) { | ||
| std::vector<Future<R>> futures(inputs.size()); | ||
| for (size_t i = 0; i < inputs.size(); ++i) { | ||
| ARROW_ASSIGN_OR_RAISE(futures[i], executor->Submit(func, i, std::move(inputs[i]))); | ||
| } | ||
| return All(std::move(futures)) | ||
| .Then([](const std::vector<Result<R>>& results) -> Result<std::vector<R>> { | ||
pitrou marked this conversation as resolved.
Outdated
Uh oh!There was an error while loading. Please reload this page. | ||
| return UnwrapOrRaise(results); | ||
| }); | ||
| } | ||
| // A parallelizer that takes a `Status(int)` function and calls it with | ||
| // arguments between 0 and `num_tasks - 1`, in sequence or in parallel, | ||
| // depending on the input boolean. | ||
| @@ -61,5 +78,25 @@ Status OptionalParallelFor(bool use_threads, int num_tasks, FUNCTION&& func, | ||
| } | ||
| } | ||
| // A parallelizer that takes a `Result<R>(int index, T item)` function and | ||
| // calls it with each item from the input array, in sequence or in parallel, | ||
| // depending on the input boolean. | ||
| template <class FUNCTION, typename T, | ||
| typename R = typename internal::call_traits::return_type<FUNCTION>::ValueType> | ||
| Future<std::vector<R>> OptionalParallelForAsync( | ||
| bool use_threads, std::vector<T> inputs, FUNCTION&& func, | ||
| Executor* executor = internal::GetCpuThreadPool()) { | ||
| if (use_threads) { | ||
| return ParallelForAsync(std::move(inputs), std::forward<FUNCTION>(func), executor); | ||
| } else { | ||
| std::vector<R> result(inputs.size()); | ||
| for (size_t i = 0; i < inputs.size(); ++i) { | ||
| ARROW_ASSIGN_OR_RAISE(result[i], func(i, inputs[i])); | ||
| } | ||
| return result; | ||
| } | ||
| } | ||
| } // namespace internal | ||
| } // namespace arrow | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -293,10 +293,12 @@ class FileReaderImpl : public FileReader { | ||
| const std::vector<int>& indices, | ||
| std::shared_ptr<Table>* table) override; | ||
| // Helper method used by ReadRowGroups/Generator - read the given row groups/columns, | ||
| // skipping bounds checks and pre-buffering. | ||
| Status DecodeRowGroups(const std::vector<int>& row_groups, | ||
| const std::vector<int>& indices, std::shared_ptr<Table>* table); | ||
| // Helper method used by ReadRowGroups - read the given row groups/columns, skipping | ||
| // bounds checks and pre-buffering. Takes a shared_ptr to self to keep the reader | ||
| // alive in async contexts. | ||
| Future<std::shared_ptr<Table>> DecodeRowGroups( | ||
| std::shared_ptr<FileReaderImpl> self, const std::vector<int>& row_groups, | ||
| const std::vector<int>& column_indices, ::arrow::internal::Executor* cpu_executor); | ||
| Status ReadRowGroups(const std::vector<int>& row_groups, | ||
| std::shared_ptr<Table>* table) override { | ||
| @@ -1007,10 +1009,9 @@ class RowGroupGenerator { | ||
| return SubmitRead(cpu_executor_, reader, row_group, column_indices); | ||
| } | ||
| auto ready = reader->parquet_reader()->WhenBuffered({row_group}, column_indices); | ||
| // TODO(ARROW-12916): always transfer here | ||
| if (cpu_executor_) ready = cpu_executor_->Transfer(ready); | ||
| return ready.Then([=]() -> ::arrow::Result<RecordBatchGenerator> { | ||
| return ReadOneRowGroup(reader, row_group, column_indices); | ||
| if (cpu_executor_) ready = cpu_executor_->TransferAlways(ready); | ||
| return ready.Then([=]() -> ::arrow::Future<RecordBatchGenerator> { | ||
| return ReadOneRowGroup(cpu_executor_, reader, row_group, column_indices); | ||
| }); | ||
| } | ||
| @@ -1024,31 +1025,25 @@ class RowGroupGenerator { | ||
| ::arrow::internal::Executor* cpu_executor, std::shared_ptr<FileReaderImpl> self, | ||
| const int row_group, const std::vector<int>& column_indices) { | ||
| if (!cpu_executor) { | ||
| return Future<RecordBatchGenerator>::MakeFinished( | ||
| ReadOneRowGroup(self, row_group, column_indices)); | ||
| return ReadOneRowGroup(cpu_executor, self, row_group, column_indices); | ||
| } | ||
| // If we have an executor, then force transfer (even if I/O was complete) | ||
| return ::arrow::DeferNotOk( | ||
| cpu_executor->Submit(ReadOneRowGroup, self, row_group, column_indices)); | ||
| return ::arrow::DeferNotOk(cpu_executor->Submit(ReadOneRowGroup, cpu_executor, self, | ||
| row_group, column_indices)); | ||
| } | ||
| static ::arrow::Result<RecordBatchGenerator> ReadOneRowGroup( | ||
| std::shared_ptr<FileReaderImpl> self, const int row_group, | ||
| const std::vector<int>& column_indices) { | ||
| std::shared_ptr<::arrow::Table> table; | ||
| static ::arrow::Future<RecordBatchGenerator> ReadOneRowGroup( | ||
| ::arrow::internal::Executor* cpu_executor, std::shared_ptr<FileReaderImpl> self, | ||
| const int row_group, const std::vector<int>& column_indices) { | ||
| // Skips bound checks/pre-buffering, since we've done that already | ||
| RETURN_NOT_OK(self->DecodeRowGroups({row_group}, column_indices, &table)); | ||
| auto table_reader = std::make_shared<::arrow::TableBatchReader>(*table); | ||
| ::arrow::RecordBatchVector batches; | ||
| while (true) { | ||
| std::shared_ptr<::arrow::RecordBatch> batch; | ||
| RETURN_NOT_OK(table_reader->ReadNext(&batch)); | ||
| if (!batch) { | ||
| break; | ||
| } | ||
| batches.push_back(batch); | ||
| } | ||
| return ::arrow::MakeVectorGenerator(std::move(batches)); | ||
| return self->DecodeRowGroups(self, {row_group}, column_indices, cpu_executor) | ||
| .Then([](const std::shared_ptr<Table>& table) | ||
| -> ::arrow::Result<RecordBatchGenerator> { | ||
pitrou marked this conversation as resolved.
Outdated
Uh oh!There was an error while loading. Please reload this page. | ||
| ::arrow::TableBatchReader table_reader(*table); | ||
| ::arrow::RecordBatchVector batches; | ||
| RETURN_NOT_OK(table_reader.ReadAll(&batches)); | ||
| return ::arrow::MakeVectorGenerator(std::move(batches)); | ||
| }); | ||
| } | ||
| std::shared_ptr<FileReaderImpl> arrow_reader_; | ||
| @@ -1104,34 +1099,49 @@ Status FileReaderImpl::ReadRowGroups(const std::vector<int>& row_groups, | ||
| END_PARQUET_CATCH_EXCEPTIONS | ||
| } | ||
| return DecodeRowGroups(row_groups, column_indices, out); | ||
| auto fut = DecodeRowGroups(/*self=*/nullptr, row_groups, column_indices, | ||
| /*cpu_executor=*/nullptr); | ||
| ARROW_ASSIGN_OR_RAISE(*out, fut.MoveResult()); | ||
| return Status::OK(); | ||
| } | ||
| // Also used by RowGroupGenerator - skip bounds check/pre-buffer to avoid doing that twice | ||
| Status FileReaderImpl::DecodeRowGroups(const std::vector<int>& row_groups, | ||
| const std::vector<int>& column_indices, | ||
| std::shared_ptr<Table>* out) { | ||
| Future<std::shared_ptr<Table>> FileReaderImpl::DecodeRowGroups( | ||
pitrou marked this conversation as resolved.
Outdated
Uh oh!There was an error while loading. Please reload this page. | ||
| std::shared_ptr<FileReaderImpl> self, const std::vector<int>& row_groups, | ||
| const std::vector<int>& column_indices, ::arrow::internal::Executor* cpu_executor) { | ||
| // `self` is used solely to keep `this` alive in an async context - but we use this | ||
| // in a sync context too so use `this` over `self` | ||
| std::vector<std::shared_ptr<ColumnReaderImpl>> readers; | ||
| std::shared_ptr<::arrow::Schema> result_schema; | ||
| RETURN_NOT_OK(GetFieldReaders(column_indices, row_groups, &readers, &result_schema)); | ||
| ::arrow::ChunkedArrayVector columns(readers.size()); | ||
| RETURN_NOT_OK(::arrow::internal::OptionalParallelFor( | ||
| reader_properties_.use_threads(), static_cast<int>(readers.size()), [&](int i) { | ||
| return ReadColumn(static_cast<int>(i), row_groups, readers[i].get(), &columns[i]); | ||
| })); | ||
| int64_t num_rows = 0; | ||
| if (!columns.empty()) { | ||
| num_rows = columns[0]->length(); | ||
| } else { | ||
| for (int i : row_groups) { | ||
| num_rows += parquet_reader()->metadata()->RowGroup(i)->num_rows(); | ||
| // OptionalParallelForAsync requires an executor | ||
| if (!cpu_executor) cpu_executor = ::arrow::internal::GetCpuThreadPool(); | ||
| auto read_column = [row_groups, self, this](size_t i, | ||
| std::shared_ptr<ColumnReaderImpl> reader) | ||
| -> ::arrow::Result<std::shared_ptr<::arrow::ChunkedArray>> { | ||
| std::shared_ptr<::arrow::ChunkedArray> column; | ||
| RETURN_NOT_OK(ReadColumn(static_cast<int>(i), row_groups, reader.get(), &column)); | ||
| return column; | ||
| }; | ||
| auto make_table = [result_schema, row_groups, self, | ||
| this](const ::arrow::ChunkedArrayVector& columns) | ||
| -> ::arrow::Result<std::shared_ptr<Table>> { | ||
| int64_t num_rows = 0; | ||
| if (!columns.empty()) { | ||
| num_rows = columns[0]->length(); | ||
| } else { | ||
| for (int i : row_groups) { | ||
| num_rows += parquet_reader()->metadata()->RowGroup(i)->num_rows(); | ||
| } | ||
| } | ||
| } | ||
| *out = Table::Make(std::move(result_schema), std::move(columns), num_rows); | ||
| return (*out)->Validate(); | ||
| auto table = Table::Make(std::move(result_schema), columns, num_rows); | ||
| RETURN_NOT_OK(table->Validate()); | ||
| return table; | ||
| }; | ||
| return ::arrow::internal::OptionalParallelForAsync(reader_properties_.use_threads(), | ||
| std::move(readers), read_column, | ||
| cpu_executor) | ||
| .Then(std::move(make_table)); | ||
| } | ||
| std::shared_ptr<RowGroupReader> FileReaderImpl::RowGroup(int row_group_index) { | ||
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.