8295808: GrowableArray should support capacity management

Reviewed-by: aboldtch, tschatzl, sspitsyn
This commit is contained in:
Kim Barrett 2022-10-25 17:42:48 +00:00
parent 6289600fe8
commit 3a873d3c5b
8 changed files with 176 additions and 105 deletions

View file

@ -1902,7 +1902,7 @@ void GraphBuilder::check_args_for_profiling(Values* obj_args, int expected) {
bool ignored_will_link;
ciSignature* declared_signature = NULL;
ciMethod* real_target = method()->get_method_at_bci(bci(), ignored_will_link, &declared_signature);
assert(expected == obj_args->max_length() || real_target->is_method_handle_intrinsic(), "missed on arg?");
assert(expected == obj_args->capacity() || real_target->is_method_handle_intrinsic(), "missed on arg?");
#endif
}
@ -1913,7 +1913,7 @@ Values* GraphBuilder::collect_args_for_profiling(Values* args, ciMethod* target,
if (obj_args == NULL) {
return NULL;
}
int s = obj_args->max_length();
int s = obj_args->capacity();
// if called through method handle invoke, some arguments may have been popped
for (int i = start, j = 0; j < s && i < args->length(); i++) {
if (args->at(i)->type()->is_object_kind()) {
@ -3968,9 +3968,9 @@ bool GraphBuilder::try_inline_full(ciMethod* callee, bool holder_known, bool ign
int start = 0;
Values* obj_args = args_list_for_profiling(callee, start, has_receiver);
if (obj_args != NULL) {
int s = obj_args->max_length();
int s = obj_args->capacity();
// if called through method handle invoke, some arguments may have been popped
for (int i = args_base+start, j = 0; j < obj_args->max_length() && i < state()->stack_size(); ) {
for (int i = args_base+start, j = 0; j < obj_args->capacity() && i < state()->stack_size(); ) {
Value v = state()->stack_at_inc(i);
if (v->type()->is_object_kind()) {
obj_args->push(v);

View file

@ -56,10 +56,11 @@
// the vectors. The size of the table is the size of either vector.
//
// The capacity of the vectors is explicitly controlled, based on the size.
// Given N > 0 and 2^N <= size < 2^(N+1), then capacity = 2^N + k * 2^(N-1)
// Given N > 0 and 2^N <= size < 2^(N+1), then capacity <= 2^N + k * 2^(N-1)
// for the smallest integer k in [0,2] such that size <= capacity. That is,
// use a power of 2 or the midpoint between consecutive powers of 2 that is
// minimally at least size.
// minimally at least size. When adding an entry and the capacity has been
// reached, capacity is increased to the next of those values.
//
// The main benefit of this representation is that it uses less space than a
// more traditional linked-list of entry nodes representation. Such a
@ -89,12 +90,11 @@ class StringDedup::Table::Bucket {
GrowableArrayCHeap<uint, mtStringDedup> _hashes;
GrowableArrayCHeap<TableValue, mtStringDedup> _values;
void adjust_capacity(int new_capacity);
void expand_if_full();
public:
// precondition: reserve == 0 or is the result of needed_capacity.
Bucket(int reserve = 0);
explicit Bucket(int reserve = 0);
~Bucket() {
while (!_values.is_empty()) {
@ -107,7 +107,7 @@ public:
const GrowableArrayView<uint>& hashes() const { return _hashes; }
const GrowableArrayView<TableValue>& values() const { return _values; }
bool is_empty() const { return _hashes.length() == 0; }
bool is_empty() const { return _hashes.is_empty(); }
int length() const { return _hashes.length(); }
void add(uint hash_code, TableValue value) {
@ -150,33 +150,17 @@ int StringDedup::Table::Bucket::needed_capacity(int needed) {
return (needed <= low) ? low : high;
}
void StringDedup::Table::Bucket::adjust_capacity(int new_capacity) {
GrowableArrayCHeap<uint, mtStringDedup> new_hashes{new_capacity};
GrowableArrayCHeap<TableValue, mtStringDedup> new_values{new_capacity};
while (!_hashes.is_empty()) {
new_hashes.push(_hashes.pop());
new_values.push(_values.pop());
}
_hashes.swap(&new_hashes);
_values.swap(&new_values);
}
void StringDedup::Table::Bucket::expand_if_full() {
if (_hashes.length() == _hashes.max_length()) {
adjust_capacity(needed_capacity(_hashes.max_length() + 1));
if (_hashes.is_full()) {
int needed = needed_capacity(_hashes.capacity() + 1);
_hashes.reserve(needed);
_values.reserve(needed);
}
}
void StringDedup::Table::Bucket::shrink() {
if (_hashes.is_empty()) {
_hashes.clear_and_deallocate();
_values.clear_and_deallocate();
} else {
int target = needed_capacity(_hashes.length());
if (target < _hashes.max_length()) {
adjust_capacity(target);
}
}
_hashes.shrink_to_fit();
_values.shrink_to_fit();
}
StringDedup::Table::TableValue

View file

@ -256,7 +256,7 @@ static void initialize_dummy_descriptors(GrowableArray<DCmdArgumentInfo*>* array
false,
true, // a DcmdFramework "option"
false);
for (int i = 0; i < array->max_length(); ++i) {
for (int i = 0; i < array->capacity(); ++i) {
array->append(dummy);
}
}

View file

@ -487,7 +487,7 @@
/*******************/ \
\
nonstatic_field(GrowableArrayBase, _len, int) \
nonstatic_field(GrowableArrayBase, _max, int) \
nonstatic_field(GrowableArrayBase, _capacity, int) \
nonstatic_field(GrowableArray<int>, _data, int*) \
\
/********************************/ \

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 1997, 2020, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1997, 2022, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -76,23 +76,23 @@ protected:
// Current number of accessible elements
int _len;
// Current number of allocated elements
int _max;
int _capacity;
GrowableArrayBase(int initial_max, int initial_len) :
GrowableArrayBase(int capacity, int initial_len) :
_len(initial_len),
_max(initial_max) {
assert(_len >= 0 && _len <= _max, "initial_len too big");
_capacity(capacity) {
assert(_len >= 0 && _len <= _capacity, "initial_len too big");
}
~GrowableArrayBase() {}
public:
int length() const { return _len; }
int max_length() const { return _max; }
int capacity() const { return _capacity; }
bool is_empty() const { return _len == 0; }
bool is_nonempty() const { return _len != 0; }
bool is_full() const { return _len == _max; }
bool is_full() const { return _len == _capacity; }
void clear() { _len = 0; }
void trunc_to(int length) {
@ -118,8 +118,8 @@ class GrowableArrayView : public GrowableArrayBase {
protected:
E* _data; // data array
GrowableArrayView<E>(E* data, int initial_max, int initial_len) :
GrowableArrayBase(initial_max, initial_len), _data(data) {}
GrowableArrayView<E>(E* data, int capacity, int initial_len) :
GrowableArrayBase(capacity, initial_len), _data(data) {}
~GrowableArrayView() {}
@ -157,12 +157,12 @@ public:
}
E first() const {
assert(_len > 0, "empty list");
assert(_len > 0, "empty");
return _data[0];
}
E top() const {
assert(_len > 0, "empty list");
assert(_len > 0, "empty");
return _data[_len-1];
}
@ -337,7 +337,7 @@ public:
void print() const {
tty->print("Growable Array " PTR_FORMAT, p2i(this));
tty->print(": length %d (_max %d) { ", _len, _max);
tty->print(": length %d (capacity %d) { ", _len, _capacity);
for (int i = 0; i < _len; i++) {
tty->print(INTPTR_FORMAT " ", *(intptr_t*)&(_data[i]));
}
@ -360,23 +360,24 @@ template <typename E, typename Derived>
class GrowableArrayWithAllocator : public GrowableArrayView<E> {
friend class VMStructs;
void expand_to(int j);
void grow(int j);
protected:
GrowableArrayWithAllocator(E* data, int initial_max) :
GrowableArrayView<E>(data, initial_max, 0) {
for (int i = 0; i < initial_max; i++) {
GrowableArrayWithAllocator(E* data, int capacity) :
GrowableArrayView<E>(data, capacity, 0) {
for (int i = 0; i < capacity; i++) {
::new ((void*)&data[i]) E();
}
}
GrowableArrayWithAllocator(E* data, int initial_max, int initial_len, const E& filler) :
GrowableArrayView<E>(data, initial_max, initial_len) {
GrowableArrayWithAllocator(E* data, int capacity, int initial_len, const E& filler) :
GrowableArrayView<E>(data, capacity, initial_len) {
int i = 0;
for (; i < initial_len; i++) {
::new ((void*)&data[i]) E(filler);
}
for (; i < initial_max; i++) {
for (; i < capacity; i++) {
::new ((void*)&data[i]) E();
}
}
@ -385,7 +386,7 @@ protected:
public:
int append(const E& elem) {
if (this->_len == this->_max) grow(this->_len);
if (this->_len == this->_capacity) grow(this->_len);
int idx = this->_len++;
this->_data[idx] = elem;
return idx;
@ -403,7 +404,7 @@ public:
E at_grow(int i, const E& fill = E()) {
assert(0 <= i, "negative index");
if (i >= this->_len) {
if (i >= this->_max) grow(i);
if (i >= this->_capacity) grow(i);
for (int j = this->_len; j <= i; j++)
this->_data[j] = fill;
this->_len = i+1;
@ -414,7 +415,7 @@ public:
void at_put_grow(int i, const E& elem, const E& fill = E()) {
assert(0 <= i, "negative index");
if (i >= this->_len) {
if (i >= this->_max) grow(i);
if (i >= this->_capacity) grow(i);
for (int j = this->_len; j < i; j++)
this->_data[j] = fill;
this->_len = i+1;
@ -425,7 +426,7 @@ public:
// inserts the given element before the element at index i
void insert_before(const int idx, const E& elem) {
assert(0 <= idx && idx <= this->_len, "illegal index");
if (this->_len == this->_max) grow(this->_len);
if (this->_len == this->_capacity) grow(this->_len);
for (int j = this->_len - 1; j >= idx; j--) {
this->_data[j + 1] = this->_data[j];
}
@ -437,7 +438,7 @@ public:
assert(0 <= idx && idx <= this->_len, "illegal index");
int array_len = array->length();
int new_len = this->_len + array_len;
if (new_len >= this->_max) grow(new_len);
if (new_len >= this->_capacity) grow(new_len);
for (int j = this->_len - 1; j >= idx; j--) {
this->_data[j + array_len] = this->_data[j];
@ -481,40 +482,80 @@ public:
void swap(GrowableArrayWithAllocator<E, Derived>* other) {
::swap(this->_data, other->_data);
::swap(this->_len, other->_len);
::swap(this->_max, other->_max);
::swap(this->_capacity, other->_capacity);
}
// Ensure capacity is at least new_capacity.
void reserve(int new_capacity);
// Reduce capacity to length.
void shrink_to_fit();
void clear_and_deallocate();
};
template <typename E, typename Derived>
void GrowableArrayWithAllocator<E, Derived>::grow(int j) {
int old_max = this->_max;
// grow the array by increasing _max to the first power of two larger than the size we need
this->_max = next_power_of_2((uint32_t)j);
// j < _max
void GrowableArrayWithAllocator<E, Derived>::expand_to(int new_capacity) {
int old_capacity = this->_capacity;
assert(new_capacity > old_capacity,
"expected growth but %d <= %d", new_capacity, old_capacity);
this->_capacity = new_capacity;
E* newData = static_cast<Derived*>(this)->allocate();
int i = 0;
for ( ; i < this->_len; i++) ::new ((void*)&newData[i]) E(this->_data[i]);
for ( ; i < this->_max; i++) ::new ((void*)&newData[i]) E();
for (i = 0; i < old_max; i++) this->_data[i].~E();
for ( ; i < this->_capacity; i++) ::new ((void*)&newData[i]) E();
for (i = 0; i < old_capacity; i++) this->_data[i].~E();
if (this->_data != NULL) {
static_cast<Derived*>(this)->deallocate(this->_data);
}
this->_data = newData;
}
template <typename E, typename Derived>
void GrowableArrayWithAllocator<E, Derived>::grow(int j) {
// grow the array by increasing _capacity to the first power of two larger than the size we need
expand_to(next_power_of_2((uint32_t)j));
}
template <typename E, typename Derived>
void GrowableArrayWithAllocator<E, Derived>::reserve(int new_capacity) {
if (new_capacity > this->_capacity) {
expand_to(new_capacity);
}
}
template <typename E, typename Derived>
void GrowableArrayWithAllocator<E, Derived>::shrink_to_fit() {
int old_capacity = this->_capacity;
int len = this->_len;
assert(len <= old_capacity, "invariant");
// If already at full capacity, nothing to do.
if (len == old_capacity) {
return;
}
// If not empty, allocate new, smaller, data, and copy old data to it.
E* old_data = this->_data;
E* new_data = nullptr;
this->_capacity = len; // Must preceed allocate().
if (len > 0) {
new_data = static_cast<Derived*>(this)->allocate();
for (int i = 0; i < len; ++i) ::new (&new_data[i]) E(old_data[i]);
}
// Destroy contents of old data, and deallocate it.
for (int i = 0; i < old_capacity; ++i) old_data[i].~E();
if (old_data != nullptr) {
static_cast<Derived*>(this)->deallocate(old_data);
}
// Install new data, which might be nullptr.
this->_data = new_data;
}
template <typename E, typename Derived>
void GrowableArrayWithAllocator<E, Derived>::clear_and_deallocate() {
if (this->_data != NULL) {
for (int i = 0; i < this->_max; i++) {
this->_data[i].~E();
}
static_cast<Derived*>(this)->deallocate(this->_data);
this->_data = NULL;
}
this->_len = 0;
this->_max = 0;
this->clear();
this->shrink_to_fit();
}
class GrowableArrayResourceAllocator {
@ -661,15 +702,15 @@ class GrowableArray : public GrowableArrayWithAllocator<E, GrowableArray<E> > {
E* allocate() {
if (on_stack()) {
debug_only(_metadata.on_stack_alloc_check());
return allocate(this->_max);
return allocate(this->_capacity);
}
if (on_C_heap()) {
return allocate(this->_max, _metadata.memflags());
return allocate(this->_capacity, _metadata.memflags());
}
assert(on_arena(), "Sanity");
return allocate(this->_max, _metadata.arena());
return allocate(this->_capacity, _metadata.arena());
}
void deallocate(E* mem) {
@ -679,26 +720,26 @@ class GrowableArray : public GrowableArrayWithAllocator<E, GrowableArray<E> > {
}
public:
GrowableArray(int initial_max = 2, MEMFLAGS memflags = mtNone) :
GrowableArray(int initial_capacity = 2, MEMFLAGS memflags = mtNone) :
GrowableArrayWithAllocator<E, GrowableArray<E> >(
allocate(initial_max, memflags),
initial_max),
allocate(initial_capacity, memflags),
initial_capacity),
_metadata(memflags) {
init_checks();
}
GrowableArray(int initial_max, int initial_len, const E& filler, MEMFLAGS memflags = mtNone) :
GrowableArray(int initial_capacity, int initial_len, const E& filler, MEMFLAGS memflags = mtNone) :
GrowableArrayWithAllocator<E, GrowableArray<E> >(
allocate(initial_max, memflags),
initial_max, initial_len, filler),
allocate(initial_capacity, memflags),
initial_capacity, initial_len, filler),
_metadata(memflags) {
init_checks();
}
GrowableArray(Arena* arena, int initial_max, int initial_len, const E& filler) :
GrowableArray(Arena* arena, int initial_capacity, int initial_len, const E& filler) :
GrowableArrayWithAllocator<E, GrowableArray<E> >(
allocate(initial_max, arena),
initial_max, initial_len, filler),
allocate(initial_capacity, arena),
initial_capacity, initial_len, filler),
_metadata(arena) {
init_checks();
}
@ -728,7 +769,7 @@ class GrowableArrayCHeap : public GrowableArrayWithAllocator<E, GrowableArrayCHe
NONCOPYABLE(GrowableArrayCHeap);
E* allocate() {
return allocate(this->_max, F);
return allocate(this->_capacity, F);
}
void deallocate(E* mem) {
@ -736,15 +777,15 @@ class GrowableArrayCHeap : public GrowableArrayWithAllocator<E, GrowableArrayCHe
}
public:
GrowableArrayCHeap(int initial_max = 0) :
GrowableArrayCHeap(int initial_capacity = 0) :
GrowableArrayWithAllocator<E, GrowableArrayCHeap<E, F> >(
allocate(initial_max, F),
initial_max) {}
allocate(initial_capacity, F),
initial_capacity) {}
GrowableArrayCHeap(int initial_max, int initial_len, const E& filler) :
GrowableArrayCHeap(int initial_capacity, int initial_len, const E& filler) :
GrowableArrayWithAllocator<E, GrowableArrayCHeap<E, F> >(
allocate(initial_max, F),
initial_max, initial_len, filler) {}
allocate(initial_capacity, F),
initial_capacity, initial_len, filler) {}
~GrowableArrayCHeap() {
this->clear_and_deallocate();

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2011, 2020, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2011, 2022, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -43,15 +43,15 @@ public class GenericGrowableArray extends VMObject {
private static synchronized void initialize(TypeDataBase db) throws WrongTypeException {
Type type = db.lookupType("GrowableArrayBase");
_max_field = new CIntField(type.getCIntegerField("_max"), 0);
_capacity_field = new CIntField(type.getCIntegerField("_capacity"), 0);
_len_field = new CIntField(type.getCIntegerField("_len"), 0);
}
private static CIntField _max_field;
private static CIntField _capacity_field;
private static CIntField _len_field;
public int max() {
return (int)_max_field.getValue(getAddress());
public int capacity() {
return (int)_capacity_field.getValue(getAddress());
}
public int length() {

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2017, 2022, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -39,11 +39,11 @@ TEST(ZArray, sanity) {
// Check size
ASSERT_EQ(a.length(), 0);
ASSERT_EQ(a.max_length(), 0);
ASSERT_EQ(a.capacity(), 0);
ASSERT_EQ(a.is_empty(), true);
ASSERT_EQ(b.length(), 10);
ASSERT_GE(b.max_length(), 10);
ASSERT_GE(b.capacity(), 10);
ASSERT_EQ(b.is_empty(), false);
// Clear elements
@ -51,14 +51,14 @@ TEST(ZArray, sanity) {
// Check that b is unaffected
ASSERT_EQ(b.length(), 10);
ASSERT_GE(b.max_length(), 10);
ASSERT_GE(b.capacity(), 10);
ASSERT_EQ(b.is_empty(), false);
a.append(1);
// Check that b is unaffected
ASSERT_EQ(b.length(), 10);
ASSERT_GE(b.max_length(), 10);
ASSERT_GE(b.capacity(), 10);
ASSERT_EQ(b.is_empty(), false);
}

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020, 2021, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2020, 2022, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -124,6 +124,43 @@ protected:
ASSERT_EQ(counter, 10);
}
template <typename ArrayClass>
static void test_capacity(ArrayClass* a) {
ASSERT_EQ(a->length(), 0);
a->reserve(50);
ASSERT_EQ(a->length(), 0);
ASSERT_EQ(a->capacity(), 50);
for (int i = 0; i < 50; ++i) {
a->append(i);
}
ASSERT_EQ(a->length(), 50);
ASSERT_EQ(a->capacity(), 50);
a->append(50);
ASSERT_EQ(a->length(), 51);
int capacity = a->capacity();
ASSERT_GE(capacity, 51);
for (int i = 0; i < 30; ++i) {
a->pop();
}
ASSERT_EQ(a->length(), 21);
ASSERT_EQ(a->capacity(), capacity);
a->shrink_to_fit();
ASSERT_EQ(a->length(), 21);
ASSERT_EQ(a->capacity(), 21);
a->reserve(50);
ASSERT_EQ(a->length(), 21);
ASSERT_EQ(a->capacity(), 50);
a->clear();
ASSERT_EQ(a->length(), 0);
ASSERT_EQ(a->capacity(), 50);
a->shrink_to_fit();
ASSERT_EQ(a->length(), 0);
ASSERT_EQ(a->capacity(), 0);
}
template <typename ArrayClass>
static void test_copy1(ArrayClass* a) {
ASSERT_EQ(a->length(), 1);
@ -200,7 +237,8 @@ protected:
enum TestEnum {
Append,
Clear,
Iterator,
Capacity,
Iterator
};
template <typename ArrayClass>
@ -214,6 +252,10 @@ protected:
test_clear(a);
break;
case Capacity:
test_capacity(a);
break;
case Iterator:
test_iterator(a);
break;
@ -402,6 +444,10 @@ TEST_VM_F(GrowableArrayTest, clear) {
with_all_types_all_0(Clear);
}
TEST_VM_F(GrowableArrayTest, capacity) {
with_all_types_all_0(Capacity);
}
TEST_VM_F(GrowableArrayTest, iterator) {
with_all_types_all_0(Iterator);
}