-
Notifications
You must be signed in to change notification settings - Fork 6.1k
8319115: GrowableArray: Do not initialize up to capacity #16918
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
Conversation
|
👋 Welcome back epeter! A progress list of the required criteria for merging this PR into |
|
@eme64 this pull request can not be integrated into git checkout JDK-8319115
git fetch https://git.openjdk.org/jdk.git master
git merge FETCH_HEAD
# resolve conflicts and follow the instructions given by git merge
git commit -m "Merge master"
git push |
|
@eme64 Is it feasible to split this up to solve each of the problems you identify in stages? There is also overlap here with JDK-8319709 IIUC. Thanks. |
|
@dholmes-ora
The first 2 items are inseparable, I cannot make substantial changes to many GrowableArray methods without there even being tests for them. And the tests would not pass before the changes for item 1, since the tests also verify what elements of the array are initialized. So adding the tests first would not be very feasible. The 3rd item could maybe be split, and be done before the rest. Though it would also require lots of changes to the test, which then I would have to completely refactor with items 1+2 anyway. And the items are related conceptually, that is why I would felt ok pushing them together. Hence: feasible probably, but lots of work overhead. Do you think it is worth it? I am aware of JDK-8319709, and in conversation with @jdksjolen - I basically took this item over for him ;) |
I too would prefer that it be split up. It's very easy to miss important details in amongst all the mostly relatively |
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.
That's it for today. I'll continue looking at this tomorrow.
| BitMap(bm_word_t* map, idx_t size_in_bits) : _map(map), _size(size_in_bits) { | ||
| verify_size(size_in_bits); | ||
| } | ||
| ~BitMap() {} |
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 change is incorrect. This destructor is intentionally declared protected, to prevent slicing
through it. It would be reasonable to change it to have a = default definition though, rather
than the empty body definition it currently has.
Note that BitMap copying has the same shallow-copying problems as GrowableArray.
| assert(_len >= 0 && _len <= _capacity, "initial_len too big"); | ||
| } | ||
|
|
||
| ~GrowableArrayBase() {} |
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.
Another incorrect removal of an intentionally protected destructor.
| GrowableArrayView<E>(E* data, int capacity, int initial_len) : | ||
| GrowableArrayBase(capacity, initial_len), _data(data) {} | ||
|
|
||
| ~GrowableArrayView() {} |
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.
Another incorrect removal of an intentionally protected destructor.
| // of the space uninitialized / no construction. | ||
| } | ||
|
|
||
| ~GrowableArrayWithAllocator() {} |
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.
Another incorrect removal of an intentionally protected destructor.
| // Hence, we need to use placement new, with copy-construct. | ||
| // Assignment would be wrong, as it assumes the destination | ||
| // was already initialized. | ||
| ::new ((void*)&this->_data[idx]) E(elem); |
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.
I don't think the cast to void* is needed, and just adds clutter. There are many more of these.
| // Copy-construct last element to deleted slot | ||
| ::new ((void*)&this->_data[index]) E(_data[_len]); | ||
| // Destruct last element | ||
| this->_data[_len].~E(); |
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.
Must not do the copy/destruct if index designated the last element.
| // Remove all elements in the range [start - end). The order is preserved. | ||
| void remove_range(int start, int end) { | ||
| assert(0 <= start, "illegal start index %d", start); | ||
| assert(start < end && end <= _len, "erase called with invalid range (%d, %d) for length %d", start, end, _len); |
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.
pre-existing: I think start == end should be permitted. There's no reason to forbid an empty range,
and there are algorithms that are simpler if empty ranges are permitted.
| } | ||
| // sort by fixed-stride sub arrays: | ||
| void sort(int f(E*, E*), int stride) { | ||
| qsort(_data, length() / stride, sizeof(E) * stride, (_sort_Fn)f); |
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.
pre-existing: Use of qsort presumes E is trivially copyable/assignable. Use QuickSort::sort instead.
|
Splitting out part 3 would have been preferable IMO. The CHeap changes are unrelated to the capacity issue and should have their own JBS issue. |
|
@kimbarrett @dholmes-ora I will try to split out the GrowableArray cheap -> GrowableArrayCHeap changes. |
|
Filed: Will work on that first, and then come back here later. |
|
@kimbarrett @dholmes-ora I just published this: #17160 |
|
@eme64 This pull request has been inactive for more than 4 weeks and will be automatically closed if another 4 weeks passes without any activity. To avoid this, simply add a new comment to the pull request. Feel free to ask for assistance if you need help with progressing this pull request towards integration! |
|
@eme64 This pull request has been inactive for more than 8 weeks and will now be automatically closed. If you would like to continue working on this pull request in the future, feel free to reopen it! This can be done using the |
Before this patch, we always initialized the GrowableArray up to its
capacity, and not just up tolength. This is problematic for a few reasons:std::vectoralso only initializes the elements up to its size, and not to capacity.append(where placement copy-construct would be expected), and not just in true assignment kinds of cases likeat_put.For this reason, I reworked a lot of the methods to ensure that only the "slots" up to
lengthare ever initialized, and the space betweenlengthandcapacityis always garbage.Also, before this patch, one can CHeap allocate both with
GrowableArrayandGrowableArrayCHeap. This is unnecessary. It required more complex verification inGrowableArrayto deal with all cases. AndGrowableArrayCHeapis already explicitly a smaller object, and should hence be preferred. Hence I changed all CHeap allocating cases ofGrowableArraytoGrowableArrayCHeap. This also allows for a clear separation:GrowableArrayonly deals with arena / resource area allocation. These are arrays that are regularly abandoned at the end of their use, rather than deleted or even cleared.GrowableArrayCHeaponly deals with CHeap allocated memory. We expect that the destructor for it is called eventually, either when it goes out of scope or whendeleteis explicitly called. We expect that the elements could be allocating resources internally, and hence rely on the destructors for the elements being called, which may free up those internally allocated resources.Therefore, we now only allow
GrowableArrayCHeapto have element types with non-trivial destructors, butGrowableArraychecks that element types do not have non-trivial destructors (since it is common practice to just abandon arena / resource area allocated arrays, rather than calling the destructor or clearing the array, which also destructs all elements). This more clearly separates the two worlds: clean-up your own mess (CHeap) vs abandon your mess (arena / resource area).I also completely refactored and improved the tests for
GrowableArray(CHeap):jdk/test/hotspot/gtest/utilities/test_growableArray.cpp
Lines 29 to 60 in e5eb360
The main improvement is that now all
GrowableArraymethods are tested, and that we test it with many different element types (including such without default-constructor or copy-assign-constructor). And we also check that the number of live elements is correct, which we can compute aslive = constructred - destructed. This is especially valuable because I refactored the use of constructors/destructors heavily, to do the change from initializing up tolengthinstead ofcapacity.Note on move-semantics
Since move semantics is currently not allowed by the style guide, we have to "simulate" a move by placement new with copy-constructor, and then destruct the old element. See this example when we need to grow an array, and move the elements from the old data to the new data:
jdk/src/hotspot/share/utilities/growableArray.hpp
Lines 530 to 563 in e5eb360
Of course this is nothing new with my change here. I just want to record that we are doing it this way, and in fact have to do so without any move-semantics.
The problem with this: If you use nested
GrowableArray<GrowableArray<E>>, then the inner arrays get copy-constructed around when we re-allocate the outer array.We now have two choices for how
GrowableArraycould copy (currently it is a shallow-copy):GrowableArrayCHeap, since it would deallocate the data of the old inner arrays, and the new inner array would still have a pointer to that deallocated data (bad!). But shallow-copy is generally dangerous, since the copy-constructor may be used in non-obvious cases:Neither of these options is good. This is exactly why the move-semantics were introduced in
C++11. We should therefore discuss the introduction of move-semantics, and weigh it against the additional complexity that it introduces.Testing:
tier1-3 and stress testing. Running.
Progress
Integration blocker
Issue
Reviewing
Using
gitCheckout this PR locally:
$ git fetch https://git.openjdk.org/jdk.git pull/16918/head:pull/16918$ git checkout pull/16918Update a local copy of the PR:
$ git checkout pull/16918$ git pull https://git.openjdk.org/jdk.git pull/16918/headUsing Skara CLI tools
Checkout this PR locally:
$ git pr checkout 16918View PR using the GUI difftool:
$ git pr show -t 16918Using diff file
Download this PR as a diff file:
https://git.openjdk.org/jdk/pull/16918.diff
Webrev
Link to Webrev Comment