Skip to content

Commit b88b69e

Browse files
committed
ARROW-20: Add null_count_ member to array containers, remove nullable_ member
Based off of ARROW-19. After some contemplation / discussion, I believe it would be better to track nullability at the schema metadata level (if at all!) rather than making it a property of the data structures. This allows the data containers to be "plain ol' data" and thus both nullable data with `null_count == 0` and non-nullable data (implicitly `null_count == 0`) can be treated as semantically equivalent in algorithms code. If it is deemed useful we can validate (cheaply) that physical data meets the metadata requirements (e.g. non-nullable type metadata cannot be associated with data containers having nulls). Author: Wes McKinney <wesm@apache.org> Closes #9 from wesm/ARROW-20 and squashes the following commits: 98be016 [Wes McKinney] ARROW-20: Add null_count_ member to Array containers, remove nullable member
1 parent e418020 commit b88b69e

25 files changed

Lines changed: 265 additions & 253 deletions

cpp/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ endif()
9292
# For CMAKE_BUILD_TYPE=Release
9393
# -O3: Enable all compiler optimizations
9494
# -g: Enable symbols for profiler tools (TODO: remove for shipping)
95-
set(CXX_FLAGS_DEBUG "-ggdb")
95+
set(CXX_FLAGS_DEBUG "-ggdb -O0")
9696
set(CXX_FLAGS_FASTDEBUG "-ggdb -O1")
9797
set(CXX_FLAGS_RELEASE "-O3 -g -DNDEBUG")
9898

cpp/src/arrow/array-test.cc

Lines changed: 28 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020
#include <cstdint>
2121
#include <cstdlib>
2222
#include <memory>
23-
#include <string>
2423
#include <vector>
2524

2625
#include "arrow/array.h"
@@ -32,60 +31,60 @@
3231
#include "arrow/util/memory-pool.h"
3332
#include "arrow/util/status.h"
3433

35-
using std::string;
36-
using std::vector;
37-
3834
namespace arrow {
3935

4036
static TypePtr int32 = TypePtr(new Int32Type());
41-
static TypePtr int32_nn = TypePtr(new Int32Type(false));
42-
4337

4438
class TestArray : public ::testing::Test {
4539
public:
4640
void SetUp() {
4741
pool_ = GetDefaultMemoryPool();
48-
49-
auto data = std::make_shared<PoolBuffer>(pool_);
50-
auto nulls = std::make_shared<PoolBuffer>(pool_);
51-
52-
ASSERT_OK(data->Resize(400));
53-
ASSERT_OK(nulls->Resize(128));
54-
55-
arr_.reset(new Int32Array(100, data, nulls));
5642
}
5743

5844
protected:
5945
MemoryPool* pool_;
60-
std::unique_ptr<Int32Array> arr_;
6146
};
6247

6348

64-
TEST_F(TestArray, TestNullable) {
65-
std::shared_ptr<Buffer> tmp = arr_->data();
66-
std::unique_ptr<Int32Array> arr_nn(new Int32Array(100, tmp));
49+
TEST_F(TestArray, TestNullCount) {
50+
auto data = std::make_shared<PoolBuffer>(pool_);
51+
auto nulls = std::make_shared<PoolBuffer>(pool_);
6752

68-
ASSERT_TRUE(arr_->nullable());
69-
ASSERT_FALSE(arr_nn->nullable());
53+
std::unique_ptr<Int32Array> arr(new Int32Array(100, data, 10, nulls));
54+
ASSERT_EQ(10, arr->null_count());
55+
56+
std::unique_ptr<Int32Array> arr_no_nulls(new Int32Array(100, data));
57+
ASSERT_EQ(0, arr_no_nulls->null_count());
7058
}
7159

7260

7361
TEST_F(TestArray, TestLength) {
74-
ASSERT_EQ(arr_->length(), 100);
62+
auto data = std::make_shared<PoolBuffer>(pool_);
63+
std::unique_ptr<Int32Array> arr(new Int32Array(100, data));
64+
ASSERT_EQ(arr->length(), 100);
7565
}
7666

7767
TEST_F(TestArray, TestIsNull) {
78-
vector<uint8_t> nulls = {1, 0, 1, 1, 0, 1, 0, 0,
79-
1, 0, 1, 1, 0, 1, 0, 0,
80-
1, 0, 1, 1, 0, 1, 0, 0,
81-
1, 0, 1, 1, 0, 1, 0, 0,
82-
1, 0, 0, 1};
68+
std::vector<uint8_t> nulls = {1, 0, 1, 1, 0, 1, 0, 0,
69+
1, 0, 1, 1, 0, 1, 0, 0,
70+
1, 0, 1, 1, 0, 1, 0, 0,
71+
1, 0, 1, 1, 0, 1, 0, 0,
72+
1, 0, 0, 1};
73+
int32_t null_count = 0;
74+
for (uint8_t x : nulls) {
75+
if (x > 0) ++null_count;
76+
}
8377

84-
std::shared_ptr<Buffer> null_buf = bytes_to_null_buffer(nulls.data(), nulls.size());
78+
std::shared_ptr<Buffer> null_buf = bytes_to_null_buffer(nulls.data(),
79+
nulls.size());
8580
std::unique_ptr<Array> arr;
86-
arr.reset(new Array(int32, nulls.size(), null_buf));
81+
arr.reset(new Array(int32, nulls.size(), null_count, null_buf));
82+
83+
ASSERT_EQ(null_count, arr->null_count());
84+
ASSERT_EQ(5, null_buf->size());
85+
86+
ASSERT_TRUE(arr->nulls()->Equals(*null_buf.get()));
8787

88-
ASSERT_EQ(null_buf->size(), 5);
8988
for (size_t i = 0; i < nulls.size(); ++i) {
9089
ASSERT_EQ(static_cast<bool>(nulls[i]), arr->IsNull(i));
9190
}

cpp/src/arrow/array.cc

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,25 +17,26 @@
1717

1818
#include "arrow/array.h"
1919

20+
#include <cstdint>
21+
2022
#include "arrow/util/buffer.h"
2123

2224
namespace arrow {
2325

2426
// ----------------------------------------------------------------------
2527
// Base array class
2628

27-
Array::Array(const TypePtr& type, int64_t length,
29+
Array::Array(const TypePtr& type, int32_t length, int32_t null_count,
2830
const std::shared_ptr<Buffer>& nulls) {
29-
Init(type, length, nulls);
31+
Init(type, length, null_count, nulls);
3032
}
3133

32-
void Array::Init(const TypePtr& type, int64_t length,
34+
void Array::Init(const TypePtr& type, int32_t length, int32_t null_count,
3335
const std::shared_ptr<Buffer>& nulls) {
3436
type_ = type;
3537
length_ = length;
38+
null_count_ = null_count;
3639
nulls_ = nulls;
37-
38-
nullable_ = type->nullable;
3940
if (nulls_) {
4041
null_bits_ = nulls_->data();
4142
}

cpp/src/arrow/array.h

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -30,38 +30,49 @@ namespace arrow {
3030
class Buffer;
3131

3232
// Immutable data array with some logical type and some length. Any memory is
33-
// owned by the respective Buffer instance (or its parents). May or may not be
34-
// nullable.
33+
// owned by the respective Buffer instance (or its parents).
3534
//
36-
// The base class only has a null array (if the data type is nullable)
35+
// The base class is only required to have a nulls buffer if the null count is
36+
// greater than 0
3737
//
3838
// Any buffers used to initialize the array have their references "stolen". If
3939
// you wish to use the buffer beyond the lifetime of the array, you need to
4040
// explicitly increment its reference count
4141
class Array {
4242
public:
43-
Array() : length_(0), nulls_(nullptr), null_bits_(nullptr) {}
44-
Array(const TypePtr& type, int64_t length,
43+
Array() :
44+
null_count_(0),
45+
length_(0),
46+
nulls_(nullptr),
47+
null_bits_(nullptr) {}
48+
49+
Array(const TypePtr& type, int32_t length, int32_t null_count = 0,
4550
const std::shared_ptr<Buffer>& nulls = nullptr);
4651

4752
virtual ~Array() {}
4853

49-
void Init(const TypePtr& type, int64_t length, const std::shared_ptr<Buffer>& nulls);
54+
void Init(const TypePtr& type, int32_t length, int32_t null_count,
55+
const std::shared_ptr<Buffer>& nulls);
5056

51-
// Determine if a slot if null. For inner loops. Does *not* boundscheck
52-
bool IsNull(int64_t i) const {
53-
return nullable_ && util::get_bit(null_bits_, i);
57+
// Determine if a slot is null. For inner loops. Does *not* boundscheck
58+
bool IsNull(int i) const {
59+
return null_count_ > 0 && util::get_bit(null_bits_, i);
5460
}
5561

56-
int64_t length() const { return length_;}
57-
bool nullable() const { return nullable_;}
62+
int32_t length() const { return length_;}
63+
int32_t null_count() const { return null_count_;}
64+
5865
const TypePtr& type() const { return type_;}
5966
TypeEnum type_enum() const { return type_->type;}
6067

68+
const std::shared_ptr<Buffer>& nulls() const {
69+
return nulls_;
70+
}
71+
6172
protected:
6273
TypePtr type_;
63-
bool nullable_;
64-
int64_t length_;
74+
int32_t null_count_;
75+
int32_t length_;
6576

6677
std::shared_ptr<Buffer> nulls_;
6778
const uint8_t* null_bits_;

cpp/src/arrow/builder.cc

Lines changed: 15 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -25,34 +25,29 @@
2525

2626
namespace arrow {
2727

28-
Status ArrayBuilder::Init(int64_t capacity) {
28+
Status ArrayBuilder::Init(int32_t capacity) {
2929
capacity_ = capacity;
30-
31-
if (nullable_) {
32-
int64_t to_alloc = util::ceil_byte(capacity) / 8;
33-
nulls_ = std::make_shared<PoolBuffer>(pool_);
34-
RETURN_NOT_OK(nulls_->Resize(to_alloc));
35-
null_bits_ = nulls_->mutable_data();
36-
memset(null_bits_, 0, to_alloc);
37-
}
30+
int32_t to_alloc = util::ceil_byte(capacity) / 8;
31+
nulls_ = std::make_shared<PoolBuffer>(pool_);
32+
RETURN_NOT_OK(nulls_->Resize(to_alloc));
33+
null_bits_ = nulls_->mutable_data();
34+
memset(null_bits_, 0, to_alloc);
3835
return Status::OK();
3936
}
4037

41-
Status ArrayBuilder::Resize(int64_t new_bits) {
42-
if (nullable_) {
43-
int64_t new_bytes = util::ceil_byte(new_bits) / 8;
44-
int64_t old_bytes = nulls_->size();
45-
RETURN_NOT_OK(nulls_->Resize(new_bytes));
46-
null_bits_ = nulls_->mutable_data();
47-
if (old_bytes < new_bytes) {
48-
memset(null_bits_ + old_bytes, 0, new_bytes - old_bytes);
49-
}
38+
Status ArrayBuilder::Resize(int32_t new_bits) {
39+
int32_t new_bytes = util::ceil_byte(new_bits) / 8;
40+
int32_t old_bytes = nulls_->size();
41+
RETURN_NOT_OK(nulls_->Resize(new_bytes));
42+
null_bits_ = nulls_->mutable_data();
43+
if (old_bytes < new_bytes) {
44+
memset(null_bits_ + old_bytes, 0, new_bytes - old_bytes);
5045
}
5146
return Status::OK();
5247
}
5348

54-
Status ArrayBuilder::Advance(int64_t elements) {
55-
if (nullable_ && length_ + elements > capacity_) {
49+
Status ArrayBuilder::Advance(int32_t elements) {
50+
if (length_ + elements > capacity_) {
5651
return Status::Invalid("Builder must be expanded");
5752
}
5853
length_ += elements;

cpp/src/arrow/builder.h

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -32,16 +32,17 @@ class Array;
3232
class MemoryPool;
3333
class PoolBuffer;
3434

35-
static constexpr int64_t MIN_BUILDER_CAPACITY = 1 << 8;
35+
static constexpr int32_t MIN_BUILDER_CAPACITY = 1 << 8;
3636

3737
// Base class for all data array builders
3838
class ArrayBuilder {
3939
public:
4040
explicit ArrayBuilder(MemoryPool* pool, const TypePtr& type) :
4141
pool_(pool),
4242
type_(type),
43-
nullable_(type_->nullable),
44-
nulls_(nullptr), null_bits_(nullptr),
43+
nulls_(nullptr),
44+
null_count_(0),
45+
null_bits_(nullptr),
4546
length_(0),
4647
capacity_(0) {}
4748

@@ -57,21 +58,21 @@ class ArrayBuilder {
5758
return children_.size();
5859
}
5960

60-
int64_t length() const { return length_;}
61-
int64_t capacity() const { return capacity_;}
62-
bool nullable() const { return nullable_;}
61+
int32_t length() const { return length_;}
62+
int32_t null_count() const { return null_count_;}
63+
int32_t capacity() const { return capacity_;}
6364

6465
// Allocates requires memory at this level, but children need to be
6566
// initialized independently
66-
Status Init(int64_t capacity);
67+
Status Init(int32_t capacity);
6768

68-
// Resizes the nulls array (if nullable)
69-
Status Resize(int64_t new_bits);
69+
// Resizes the nulls array
70+
Status Resize(int32_t new_bits);
7071

7172
// For cases where raw data was memcpy'd into the internal buffers, allows us
7273
// to advance the length of the builder. It is your responsibility to use
7374
// this function responsibly.
74-
Status Advance(int64_t elements);
75+
Status Advance(int32_t elements);
7576

7677
const std::shared_ptr<PoolBuffer>& nulls() const { return nulls_;}
7778

@@ -83,15 +84,15 @@ class ArrayBuilder {
8384
MemoryPool* pool_;
8485

8586
TypePtr type_;
86-
bool nullable_;
8787

88-
// If the type is not nullable, then null_ is nullptr after initialization
88+
// When nulls are first appended to the builder, the null bitmap is allocated
8989
std::shared_ptr<PoolBuffer> nulls_;
90+
int32_t null_count_;
9091
uint8_t* null_bits_;
9192

9293
// Array length, so far. Also, the index of the next element to be added
93-
int64_t length_;
94-
int64_t capacity_;
94+
int32_t length_;
95+
int32_t capacity_;
9596

9697
// Child value array builders. These are owned by this class
9798
std::vector<std::unique_ptr<ArrayBuilder> > children_;

cpp/src/arrow/test-util.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,16 @@ void random_nulls(int64_t n, double pct_null, std::vector<bool>* nulls) {
8484
}
8585
}
8686

87+
static inline int null_count(const std::vector<uint8_t>& nulls) {
88+
int result = 0;
89+
for (size_t i = 0; i < nulls.size(); ++i) {
90+
if (nulls[i] > 0) {
91+
++result;
92+
}
93+
}
94+
return result;
95+
}
96+
8797
std::shared_ptr<Buffer> bytes_to_null_buffer(uint8_t* bytes, int length) {
8898
std::shared_ptr<Buffer> out;
8999

cpp/src/arrow/type.h

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -57,11 +57,9 @@ struct LayoutType {
5757
// either a primitive physical type (bytes or bits of some fixed size), a
5858
// nested type consisting of other data types, or another data type (e.g. a
5959
// timestamp encoded as an int64)
60-
//
61-
// Any data type can be nullable
6260

6361
enum class TypeEnum: char {
64-
// A degerate NULL type represented as 0 bytes/bits
62+
// A degenerate NULL type represented as 0 bytes/bits
6563
NA = 0,
6664

6765
// Little-endian integer types
@@ -138,14 +136,12 @@ enum class TypeEnum: char {
138136

139137
struct DataType {
140138
TypeEnum type;
141-
bool nullable;
142139

143-
explicit DataType(TypeEnum type, bool nullable = true)
144-
: type(type), nullable(nullable) {}
140+
explicit DataType(TypeEnum type)
141+
: type(type) {}
145142

146143
virtual bool Equals(const DataType* other) {
147-
return (this == other) || (this->type == other->type &&
148-
this->nullable == other->nullable);
144+
return this == other || this->type == other->type;
149145
}
150146

151147
virtual std::string ToString() const = 0;

cpp/src/arrow/types/collection.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ template <TypeEnum T>
2929
struct CollectionType : public DataType {
3030
std::vector<TypePtr> child_types_;
3131

32-
explicit CollectionType(bool nullable = true) : DataType(T, nullable) {}
32+
CollectionType() : DataType(T) {}
3333

3434
const TypePtr& child(int i) const {
3535
return child_types_[i];

0 commit comments

Comments
 (0)