You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@arrow.apache.org by ko...@apache.org on 2023/09/07 02:38:25 UTC
[arrow] branch main updated: GH-37515: [C++] Remove memory address optimization from `ChunkedArray::Equals(const std::shared_ptr& other)` if the `ChunkedArray` can have `NaN` values (#37579)
This is an automated email from the ASF dual-hosted git repository.
kou pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/arrow.git
The following commit(s) were added to refs/heads/main by this push:
new 85ec07e5b1 GH-37515: [C++] Remove memory address optimization from `ChunkedArray::Equals(const std::shared_ptr<arrow::ChunkedArray>& other)` if the `ChunkedArray` can have `NaN` values (#37579)
85ec07e5b1 is described below
commit 85ec07e5b104dcb198bf041652b5f3086384d06f
Author: sgilmore10 <74...@users.noreply.github.com>
AuthorDate: Wed Sep 6 22:38:19 2023 -0400
GH-37515: [C++] Remove memory address optimization from `ChunkedArray::Equals(const std::shared_ptr<arrow::ChunkedArray>& other)` if the `ChunkedArray` can have `NaN` values (#37579)
### Rationale for this change
`ChunkedArray::Equals(const std::shared_ptr<ChunkedArray>& other)` assumes that if the two `ChunkedArray`s share the same memory address, then they must be equal. However, this optimization doesn't take into account that `NaN` values are not considered equal by default. Consequently, this can lead to surprising, inconsistent results from a user's perspective. For example, `ChunkedArray::Equals(const std::shared_ptr<ChunkedArray>& other)` and `ChunkedArray::Equals(const ChunkedArray& [...]
The program below illustrates this inconsistency:
```c++
#include <arrow/api.h>
#include <arrow/type.h>
#include <iostream>
#include <math.h>
#include <sstream>
arrow::Result<std::shared_ptr<arrow::ChunkedArray>> make_chunked_array() {
arrow::NumericBuilder<arrow::DoubleType> builder;
std::shared_ptr<arrow::Array> array;
ARROW_RETURN_NOT_OK(builder.AppendValues({0, 1, NAN, 2, 4}));
ARROW_RETURN_NOT_OK(builder.Finish(&array));
return arrow::ChunkedArray::Make({array});
}
int main(int argc, char *argv[])
{
auto maybe_chunked_array = make_chunked_array();
if (!maybe_chunked_array.ok()) {
return -1;
}
auto chunked_array = std::move(maybe_chunked_array).ValueUnsafe();
auto array = chunked_array->chunk(0);
std::stringstream stream;
stream << "chunked_array contents: ";
stream << "\n\n";
stream << chunked_array->ToString();
stream << "\n\n";
stream << "chunked_array->Equals(chunked_array): ";
stream << chunked_array->Equals(chunked_array);
stream << "chunked_array->Equals(*chunked_array): ";
stream << chunked_array->Equals(*chunked_array);
std::cout << stream.str() << std::endl;
}
```
Here is the output of this program:
```shell
chunked_array contents:
[
[
0,
1,
nan,
2,
4
]
]
chunked_array->Equals(chunked_array): 1
chunked_array->Equals(*chunked_array): 0
```
### What changes are included in this PR?
Updated `ChunkedArray::Equals(const std::shared_ptr<ChunkedArray>& other)` to only return `true` early IF:
- The two share the same address AND
- They cannot have `NaN` values
If both of those conditions are not satisfied, `ChunkedArray::Equals(const std::shared_ptr<ChunkedArray>& other)` will do the element-by-element comparison.
### Are these changes tested?
Yes. I added a new test case called `EqualsSameAddressWithNaNs` to `chunked_array_test.cc`.
### Are there any user-facing changes?
Yes. `ChunkedArray::Equals(const std::shared_ptr<arrow::ChunkedArray>& other)` may return `false` even if the two `ChunkedArray`s have the same memory address. This will only occur if the `ChunkedArray`'s contain `NaN` values.
* Closes: #37515
Authored-by: Sarah Gilmore <sg...@mathworks.com>
Signed-off-by: Sutou Kouhei <ko...@clear-code.com>
---
cpp/src/arrow/chunked_array.cc | 24 +++++++++++++++++++++---
cpp/src/arrow/chunked_array_test.cc | 29 +++++++++++++++++++++++++++++
2 files changed, 50 insertions(+), 3 deletions(-)
diff --git a/cpp/src/arrow/chunked_array.cc b/cpp/src/arrow/chunked_array.cc
index c5e6d7fa4b..12937406e7 100644
--- a/cpp/src/arrow/chunked_array.cc
+++ b/cpp/src/arrow/chunked_array.cc
@@ -30,6 +30,7 @@
#include "arrow/pretty_print.h"
#include "arrow/status.h"
#include "arrow/type.h"
+#include "arrow/type_traits.h"
#include "arrow/util/checked_cast.h"
#include "arrow/util/logging.h"
@@ -111,13 +112,30 @@ bool ChunkedArray::Equals(const ChunkedArray& other) const {
.ok();
}
-bool ChunkedArray::Equals(const std::shared_ptr<ChunkedArray>& other) const {
- if (this == other.get()) {
- return true;
+namespace {
+
+bool mayHaveNaN(const arrow::DataType& type) {
+ if (type.num_fields() == 0) {
+ return is_floating(type.id());
+ } else {
+ for (const auto& field : type.fields()) {
+ if (mayHaveNaN(*field->type())) {
+ return true;
+ }
+ }
}
+ return false;
+}
+
+} // namespace
+
+bool ChunkedArray::Equals(const std::shared_ptr<ChunkedArray>& other) const {
if (!other) {
return false;
}
+ if (this == other.get() && !mayHaveNaN(*type_)) {
+ return true;
+ }
return Equals(*other.get());
}
diff --git a/cpp/src/arrow/chunked_array_test.cc b/cpp/src/arrow/chunked_array_test.cc
index 08410b4cd5..46dccaf3c6 100644
--- a/cpp/src/arrow/chunked_array_test.cc
+++ b/cpp/src/arrow/chunked_array_test.cc
@@ -146,6 +146,35 @@ TEST_F(TestChunkedArray, EqualsDifferingMetadata) {
ASSERT_TRUE(left.Equals(right));
}
+TEST_F(TestChunkedArray, EqualsSameAddressWithNaNs) {
+ auto chunk_with_nan1 = ArrayFromJSON(float64(), "[0, 1, 2, NaN]");
+ auto chunk_without_nan1 = ArrayFromJSON(float64(), "[3, 4, 5]");
+ ArrayVector chunks1 = {chunk_with_nan1, chunk_without_nan1};
+ ASSERT_OK_AND_ASSIGN(auto chunked_array_with_nan1, ChunkedArray::Make(chunks1));
+ ASSERT_FALSE(chunked_array_with_nan1->Equals(chunked_array_with_nan1));
+
+ auto chunk_without_nan2 = ArrayFromJSON(float64(), "[6, 7, 8, 9]");
+ ArrayVector chunks2 = {chunk_without_nan1, chunk_without_nan2};
+ ASSERT_OK_AND_ASSIGN(auto chunked_array_without_nan1, ChunkedArray::Make(chunks2));
+ ASSERT_TRUE(chunked_array_without_nan1->Equals(chunked_array_without_nan1));
+
+ auto int32_array = ArrayFromJSON(int32(), "[0, 1, 2]");
+ auto float64_array_with_nan = ArrayFromJSON(float64(), "[0, 1, NaN]");
+ ArrayVector arrays1 = {int32_array, float64_array_with_nan};
+ std::vector<std::string> fieldnames = {"Int32Type", "Float64Type"};
+ ASSERT_OK_AND_ASSIGN(auto struct_with_nan, StructArray::Make(arrays1, fieldnames));
+ ArrayVector chunks3 = {struct_with_nan};
+ ASSERT_OK_AND_ASSIGN(auto chunked_array_with_nan2, ChunkedArray::Make(chunks3));
+ ASSERT_FALSE(chunked_array_with_nan2->Equals(chunked_array_with_nan2));
+
+ auto float64_array_without_nan = ArrayFromJSON(float64(), "[0, 1, 2]");
+ ArrayVector arrays2 = {int32_array, float64_array_without_nan};
+ ASSERT_OK_AND_ASSIGN(auto struct_without_nan, StructArray::Make(arrays2, fieldnames));
+ ArrayVector chunks4 = {struct_without_nan};
+ ASSERT_OK_AND_ASSIGN(auto chunked_array_without_nan2, ChunkedArray::Make(chunks4));
+ ASSERT_TRUE(chunked_array_without_nan2->Equals(chunked_array_without_nan2));
+}
+
TEST_F(TestChunkedArray, SliceEquals) {
random::RandomArrayGenerator gen(42);