-
Notifications
You must be signed in to change notification settings - Fork 4.2k
ARROW-12730: [MATLAB] Update featherreadmex and featherwritemex to build against latest Arrow C++ APIs #10305
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 8 commits
6a19696
724e6ac
0197f93
40bd571
7a4b2a7
77a71f4
d039955
36201e7
c86c5da
bbd0ebe
d1f730b
24af511
45b1842
338a09a
141a37d
e3e40f6
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 |
|---|---|---|
|
|
@@ -15,7 +15,8 @@ | |
| # specific language governing permissions and limitations | ||
| # under the License. | ||
|
|
||
| cmake_minimum_required(VERSION 3.2) | ||
| cmake_minimum_required(VERSION 3.20) | ||
|
|
||
| set(CMAKE_CXX_STANDARD 11) | ||
|
|
||
| set(MLARROW_VERSION "5.0.0-SNAPSHOT") | ||
|
|
@@ -29,22 +30,45 @@ if(EXISTS "${CPP_CMAKE_MODULES}") | |
| set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CPP_CMAKE_MODULES}) | ||
| endif() | ||
|
|
||
| ## Arrow is Required | ||
| set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_SOURCE_DIR}/cmake_modules) | ||
|
|
||
| # Arrow is Required | ||
| find_package(Arrow REQUIRED) | ||
|
|
||
| ## MATLAB is required to be installed to build MEX interfaces | ||
| set(MATLAB_ADDITIONAL_VERSIONS "R2018a=9.4") | ||
| find_package(Matlab REQUIRED MX_LIBRARY) | ||
|
|
||
| # Build featherread mex file based on the arrow shared library | ||
| matlab_add_mex(NAME featherreadmex | ||
| SRC src/featherreadmex.cc src/feather_reader.cc src/util/handle_status.cc | ||
| src/util/unicode_conversion.cc | ||
| LINK_TO ${ARROW_SHARED_LIB}) | ||
| target_include_directories(featherreadmex PRIVATE ${ARROW_INCLUDE_DIR}) | ||
|
|
||
| # Build featherwrite mex file based on the arrow shared library | ||
| matlab_add_mex(NAME featherwritemex | ||
| SRC src/featherwritemex.cc src/feather_writer.cc src/util/handle_status.cc | ||
| LINK_TO ${ARROW_SHARED_LIB}) | ||
| target_include_directories(featherwritemex PRIVATE ${ARROW_INCLUDE_DIR}) | ||
| # MATLAB is Required | ||
| find_package(Matlab REQUIRED) | ||
|
|
||
| # Construct the absolute path to featherread's source files | ||
| set(featherread_sources featherreadmex.cc feather_reader.cc util/handle_status.cc | ||
| util/unicode_conversion.cc) | ||
| list(TRANSFORM featherread_sources PREPEND ${CMAKE_SOURCE_DIR}/src/) | ||
|
|
||
| # Build featherreadmex MEX binary | ||
| matlab_add_mex(R2018a | ||
| NAME featherreadmex | ||
| SRC ${featherread_sources} | ||
| LINK_TO arrow_shared) | ||
|
|
||
| # Construct the absolute path to featherwrite's source files | ||
| set(featherwrite_sources featherwritemex.cc feather_writer.cc util/handle_status.cc | ||
| util/unicode_conversion.cc) | ||
| list(TRANSFORM featherwrite_sources PREPEND ${CMAKE_SOURCE_DIR}/src/) | ||
|
|
||
| # Build featherwritemex MEX binary | ||
| matlab_add_mex(R2018a | ||
| NAME featherwritemex | ||
| SRC ${featherwrite_sources} | ||
| LINK_TO arrow_shared) | ||
|
|
||
| # Ensure the MEX binaries are placed in the src directory on all platforms | ||
|
Member
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. I was just wondering. Why do we need to place the binaries in the source directory?
Member
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. In order to execute the MEX function, it has to be discoverable on the MATLAB search path. We also have MATLAB code (featherread.m and featherwrite.m) that we also need to add to the MATLAB search path. Since we need to add the source directory to the path anyway, I thought it makes sense to put the MEX files there as well.
Member
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. Thanks. Generally, we should not change anything files in the source directory with out-of-source build. Can we also add the build directory to the MATLAB search path? Is it difficult?
Member
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. We could add the build folder to the MATLAB search path. However, this will make harder to run our unit tests automatically because we would require the user to explicitly tell us where their build files are located every time. Additionally, as the MATLAB interface grows, we will have many MEX files. In order to keep things organized. We see two approaches:
or
Option 2 seems more scalable, but if you're experience tells us this will lead to issues, we can revisit adding the build folder to the path. Best,
Member
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. Thanks for describing this.
Member
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. Yeah, we can definitely investigate both approaches and see which one is preferable. I created a jira task to look into this in a future pull request. |
||
| if(WIN32) | ||
| set_target_properties(featherreadmex PROPERTIES RUNTIME_OUTPUT_DIRECTORY | ||
| $<1:${CMAKE_SOURCE_DIR}/src>) | ||
| set_target_properties(featherwritemex PROPERTIES RUNTIME_OUTPUT_DIRECTORY | ||
| $<1:${CMAKE_SOURCE_DIR}/src>) | ||
| else() | ||
| set_target_properties(featherreadmex PROPERTIES LIBRARY_OUTPUT_DIRECTORY | ||
| $<1:${CMAKE_SOURCE_DIR}/src>) | ||
| set_target_properties(featherwritemex PROPERTIES LIBRARY_OUTPUT_DIRECTORY | ||
| $<1:${CMAKE_SOURCE_DIR}/src>) | ||
| endif() | ||
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -18,16 +18,21 @@ | |||||||||
| #include <algorithm> | ||||||||||
| #include <cmath> | ||||||||||
|
|
||||||||||
| #include "feather_reader.h" | ||||||||||
|
|
||||||||||
| #include <arrow/array/array_base.h> | ||||||||||
| #include <arrow/array/builder_base.h> | ||||||||||
| #include <arrow/array/builder_primitive.h> | ||||||||||
| #include <arrow/io/file.h> | ||||||||||
| #include <arrow/ipc/feather.h> | ||||||||||
| #include <arrow/result.h> | ||||||||||
| #include <arrow/status.h> | ||||||||||
| #include <arrow/table.h> | ||||||||||
| #include <arrow/type.h> | ||||||||||
| #include <arrow/util/bit-util.h> | ||||||||||
|
|
||||||||||
| #include <arrow/type_traits.h> | ||||||||||
| #include <arrow/util/bitmap_visit.h> | ||||||||||
| #include <mex.h> | ||||||||||
|
|
||||||||||
| #include "feather_reader.h" | ||||||||||
| #include "matlab_traits.h" | ||||||||||
| #include "util/handle_status.h" | ||||||||||
| #include "util/unicode_conversion.h" | ||||||||||
|
|
@@ -52,11 +57,12 @@ mxArray* ReadNumericVariableData(const std::shared_ptr<Array>& column) { | |||||||||
| mxArray* variable_data = | ||||||||||
| mxCreateNumericMatrix(column->length(), 1, matlab_class_id, mxREAL); | ||||||||||
|
|
||||||||||
| std::shared_ptr<ArrowArrayType> integer_array = | ||||||||||
| std::shared_ptr<ArrowArrayType> arrow_numeric_array = | ||||||||||
|
sgilmore10 marked this conversation as resolved.
Outdated
|
||||||||||
| std::static_pointer_cast<ArrowArrayType>(column); | ||||||||||
|
|
||||||||||
| // Get a raw pointer to the Arrow array data. | ||||||||||
| const MatlabType* source = integer_array->raw_values(); | ||||||||||
| const MatlabType* source = | ||||||||||
| reinterpret_cast<const MatlabType*>(arrow_numeric_array->values()->data()); | ||||||||||
|
Member
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. Does this work with sliced array? What is the problem of
Member
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. From the documentation, it looks like the values() method doesn't account for slice offsets. I think we modified this line when we were trying to get the code to compile again, but didn't check to see if this change was really necessary. I just tried using raw_values() instead and it works. I'll undo this change. |
||||||||||
|
|
||||||||||
| // Get a mutable pointer to the MATLAB array data and std::copy the | ||||||||||
| // Arrow array data into it. | ||||||||||
|
|
@@ -121,8 +127,7 @@ void BitUnpackBuffer(const std::shared_ptr<Buffer>& source, int64_t length, | |||||||||
| // writes to a zero-initialized destination buffer. | ||||||||||
| // Implements a fast path for the fully-valid and fully-invalid cases. | ||||||||||
| // Returns true if the destination buffer was successfully populated. | ||||||||||
| bool TryBitUnpackFastPath(const std::shared_ptr<Array>& array, | ||||||||||
| mxLogical* destination) { | ||||||||||
| bool TryBitUnpackFastPath(const std::shared_ptr<Array>& array, mxLogical* destination) { | ||||||||||
| const int64_t null_count = array->null_count(); | ||||||||||
| const int64_t length = array->length(); | ||||||||||
|
|
||||||||||
|
|
@@ -177,32 +182,32 @@ Status FeatherReader::Open(const std::string& filename, | |||||||||
| *feather_reader = std::shared_ptr<FeatherReader>(new FeatherReader()); | ||||||||||
|
|
||||||||||
| // Open file with given filename as a ReadableFile. | ||||||||||
| std::shared_ptr<io::ReadableFile> readable_file(nullptr); | ||||||||||
|
|
||||||||||
| RETURN_NOT_OK(io::ReadableFile::Open(filename, &readable_file)); | ||||||||||
| arrow::Result<std::shared_ptr<io::ReadableFile>> maybe_readable_file = | ||||||||||
| io::ReadableFile::Open(filename); | ||||||||||
|
sgilmore10 marked this conversation as resolved.
Outdated
|
||||||||||
|
|
||||||||||
| // TableReader expects a RandomAccessFile. | ||||||||||
| std::shared_ptr<io::RandomAccessFile> random_access_file(readable_file); | ||||||||||
| ARROW_ASSIGN_OR_RAISE(std::shared_ptr<io::RandomAccessFile> random_access_file, | ||||||||||
| maybe_readable_file); | ||||||||||
|
Member
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 need this cast? I think that
Member
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. You're right. We don't need this cast. |
||||||||||
|
|
||||||||||
| // Open the Feather file for reading with a TableReader. | ||||||||||
| RETURN_NOT_OK(ipc::feather::TableReader::Open(random_access_file, | ||||||||||
| &(*feather_reader)->table_reader_)); | ||||||||||
|
|
||||||||||
| // Read the table metadata from the Feather file. | ||||||||||
| (*feather_reader)->num_rows_ = (*feather_reader)->table_reader_->num_rows(); | ||||||||||
| (*feather_reader)->num_variables_ = (*feather_reader)->table_reader_->num_columns(); | ||||||||||
| (*feather_reader)->description_ = | ||||||||||
| (*feather_reader)->table_reader_->HasDescription() | ||||||||||
| ? (*feather_reader)->table_reader_->GetDescription() | ||||||||||
| : ""; | ||||||||||
|
|
||||||||||
| if ((*feather_reader)->num_rows_ > internal::MAX_MATLAB_SIZE || | ||||||||||
| (*feather_reader)->num_variables_ > internal::MAX_MATLAB_SIZE) { | ||||||||||
| mexErrMsgIdAndTxt("MATLAB:arrow:SizeTooLarge", | ||||||||||
| "The table size exceeds MATLAB limits: %u x %u", | ||||||||||
| (*feather_reader)->num_rows_, (*feather_reader)->num_variables_); | ||||||||||
| arrow::Result<std::shared_ptr<ipc::feather::Reader>> maybe_reader = | ||||||||||
| ipc::feather::Reader::Open(random_access_file); | ||||||||||
| ARROW_ASSIGN_OR_RAISE(auto reader, maybe_reader); | ||||||||||
|
Member
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.
Suggested change
Member
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. I think we still need auto there. reader's a new variable I've never declared above.
Member
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. Just made this change (with auto) in separate commit.
Member
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. Oh, sorry. You're right. |
||||||||||
|
|
||||||||||
| // Set the internal reader_ object. | ||||||||||
| (*feather_reader)->reader_ = reader; | ||||||||||
|
|
||||||||||
| // Check the feather file version | ||||||||||
| int version = reader->version(); | ||||||||||
|
sgilmore10 marked this conversation as resolved.
Outdated
|
||||||||||
| if (version == ipc::feather::kFeatherV2Version) { | ||||||||||
| return Status::NotImplemented("Support for Feather V2 has not been implemented."); | ||||||||||
| } else if (version != ipc::feather::kFeatherV1Version) { | ||||||||||
| return Status::Invalid("Unknown Feather format version."); | ||||||||||
| } | ||||||||||
|
|
||||||||||
| // read the table metadata from the Feather file | ||||||||||
| std::shared_ptr<Schema> schema = reader->schema(); | ||||||||||
| (*feather_reader)->num_variables_ = schema->num_fields(); | ||||||||||
|
sgilmore10 marked this conversation as resolved.
Outdated
|
||||||||||
| return Status::OK(); | ||||||||||
| } | ||||||||||
|
|
||||||||||
|
|
@@ -225,15 +230,11 @@ mxArray* FeatherReader::ReadMetadata() const { | |||||||||
| mxSetField(metadata, 0, "NumVariables", | ||||||||||
| mxCreateDoubleScalar(static_cast<double>(num_variables_))); | ||||||||||
|
|
||||||||||
| // Set the description. | ||||||||||
| mxSetField(metadata, 0, "Description", | ||||||||||
| util::ConvertUTF8StringToUTF16CharMatrix(description_)); | ||||||||||
|
|
||||||||||
| return metadata; | ||||||||||
| } | ||||||||||
|
|
||||||||||
| // Read the table variables from the Feather file as a mxArray*. | ||||||||||
| mxArray* FeatherReader::ReadVariables() const { | ||||||||||
| mxArray* FeatherReader::ReadVariables() { | ||||||||||
| const int32_t num_variable_fields = 4; | ||||||||||
| const char* fieldnames[] = {"Name", "Type", "Data", "Valid"}; | ||||||||||
|
|
||||||||||
|
|
@@ -242,16 +243,34 @@ mxArray* FeatherReader::ReadVariables() const { | |||||||||
| mxArray* variables = | ||||||||||
| mxCreateStructMatrix(1, num_variables_, num_variable_fields, fieldnames); | ||||||||||
|
|
||||||||||
| // Read all the table variables in the Feather file into memory. | ||||||||||
| std::shared_ptr<arrow::Table> table = nullptr; | ||||||||||
|
sgilmore10 marked this conversation as resolved.
Outdated
|
||||||||||
| arrow::Status status = reader_->Read(&table); | ||||||||||
|
sgilmore10 marked this conversation as resolved.
Outdated
|
||||||||||
| if (!status.ok()) { | ||||||||||
| std::string err_msg = | ||||||||||
| "Failed to read arrow::Table from Feather file. Reason: " + status.message(); | ||||||||||
| mexErrMsgIdAndTxt("MATLAB:arrow:FeatherReader::FailedToReadTable", err_msg.c_str()); | ||||||||||
|
sgilmore10 marked this conversation as resolved.
Outdated
|
||||||||||
| } | ||||||||||
|
|
||||||||||
| // Set the number of rows | ||||||||||
| num_rows_ = table->num_rows(); | ||||||||||
|
|
||||||||||
| if (num_rows_ > internal::MAX_MATLAB_SIZE || | ||||||||||
| num_variables_ > internal::MAX_MATLAB_SIZE) { | ||||||||||
| mexErrMsgIdAndTxt("MATLAB:arrow:SizeTooLarge", | ||||||||||
| "The table size exceeds MATLAB limits: %u x %u", num_rows_, | ||||||||||
| num_variables_); | ||||||||||
| } | ||||||||||
|
|
||||||||||
| std::vector<std::string> column_names = table->ColumnNames(); | ||||||||||
|
sgilmore10 marked this conversation as resolved.
Outdated
|
||||||||||
|
|
||||||||||
| for (int64_t i = 0; i < num_variables_; ++i) { | ||||||||||
| std::shared_ptr<ChunkedArray> column; | ||||||||||
| util::HandleStatus(table_reader_->GetColumn(i, &column)); | ||||||||||
| std::shared_ptr<ChunkedArray> column = table->column(i); | ||||||||||
|
sgilmore10 marked this conversation as resolved.
Outdated
|
||||||||||
| if (column->num_chunks() != 1) { | ||||||||||
| mexErrMsgIdAndTxt("MATLAB:arrow:FeatherReader::ReadVariables", | ||||||||||
| "Chunked columns not yet supported"); | ||||||||||
| } | ||||||||||
| std::shared_ptr<Array> chunk = column->chunk(0); | ||||||||||
| const std::string column_name = table_reader_->GetColumnName(i); | ||||||||||
| const std::string column_name = column_names[i]; | ||||||||||
|
|
||||||||||
| // set the struct fields data | ||||||||||
| mxSetField(variables, i, "Name", internal::ReadVariableName(column_name)); | ||||||||||
|
|
||||||||||
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.
This seems a bit high, is it necessary?
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.
We wanted to use 3.20 because earlier versions of the FindMatlab.cmake module had a few bugs we ran into when trying to build our mex functions. I'm open to other approaches, but the hope was to build on the existing work of the cmake community to avoid reinventing the wheel. We also want to share improvements upstream where appropriate.