-
Notifications
You must be signed in to change notification settings - Fork 8.5k
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
Replace dependency on boost with a custom small vector #13716
Conversation
Hell yeah. Why is this difficult to review? |
The code isn't that trivial tbh. I honestly thought it was pretty solid when I opened the PR, but as you can see, the tests disagree and this code remains buggy. |
Yea I mean, |
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.
Add some tests, answer the Qs, and we'll be golden!
83a3b2c
to
9f9f704
Compare
9f9f704
to
efa468b
Compare
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
@@ -57,6 +57,7 @@ locl | |||
lorem | |||
Lorigin | |||
maxed | |||
minimalistic |
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 think the word is just "minmal" :D
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'm fine with this.jpg. Minimalistic sounds like something I'd say.
Do we think I'm actually qualified to review the implementation of a basic collection like that in c++? Or should I lean on the fact that the tests passed and nothing seems to explode? Otherwise @miniksa is usually a bit more fluent in the idomatic way of implementing something like this, and might be a better reviewer for https://github.com/microsoft/terminal/pull/13716/files#diff-396eb9779e7a38e7354acd9c04395d1fb502e991b843ead351c1312156b14a5a |
This is in today's bug bash build as well! |
I can put it on my list for tomorrow. |
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.
Very exciting to be losing boost. Looks pretty good. I didn't have any blocking comments.
@@ -57,6 +57,7 @@ locl | |||
lorem | |||
Lorigin | |||
maxed | |||
minimalistic |
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'm fine with this.jpg. Minimalistic sounds like something I'd say.
// small_vector::_data can reference both non-owned (&_buffer[0]) and owned (new[]) data. | ||
#pragma warning(disable : 26402) // Return a scoped object instead of a heap-allocated if it has a move constructor (r.3). | ||
// small_vector manages a _capacity of potentially uninitialized data. We can't use regular new/delete. | ||
#pragma warning(disable : 26409) // Avoid calling new and delete explicitly, use std::make_unique<T> instead (r.11). | ||
// That's how the STL implemented their std::vector<>::iterator. I simply copied the concept. | ||
#pragma warning(disable : 26434) // Function '...' hides a non-virtual function '...'. | ||
// Functions like front()/back()/operator[]() are explicitly unchecked, just like the std::vector equivalents. | ||
#pragma warning(disable : 26446) // Prefer to use gsl::at() instead of unchecked subscript operator (bounds.4). | ||
// small_vector::_data references potentially uninitialized data and so we can't pass it regular iterators which reference initialized data. | ||
#pragma warning(disable : 26459) // You called an STL function '...' with a raw pointer parameter at position '...' that may be unsafe ... (stl.1). | ||
// small_vector::_data references potentially uninitialized data and so we can't pass it regular iterators which reference initialized data. | ||
#pragma warning(disable : 26481) // Don't use pointer arithmetic. Use span instead (bounds.1). |
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 has to be the most comprehensive explanation of warning suppressions I've ever seen. Fantastic!
|
||
namespace til | ||
{ | ||
// This class was adopted from std::span<>::iterator. |
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 would have written "stolen shamelessly" instead of "adopted". But I guess your wording is technically a better idea.
reserve(other._size); | ||
|
||
std::uninitialized_copy(other.begin(), other.end(), _uninitialized_begin()); | ||
_size = other._size; |
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.
Very good call with reporting empty on failure.
// NOTE: If an exception is thrown while copying, the vector is left empty. | ||
small_vector& operator=(const small_vector& other) | ||
{ | ||
clear(); |
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 was about to ask why you needed std::destroy
for the move constructor but not for here, but I suppose it's because the destructors will be naturally called as you're clear()
ing. Also I bet I could answer this myself if I scrolled further down.
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.
clear()
only destroys the data, but doesn't free the allocated memory.
The copy constructor wants exactly that: It wants to reuse the existing memory. But the move constructor needs to free its own memory, since it'll take ownership of the memory of the other
vector.
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.
Ah, I was thinking about calling the destructors of the objects inside when I wrote this comment not necessarily about using the memory space.
if (_capacity != N) | ||
{ | ||
_deallocate(_data); | ||
} | ||
|
||
if (other._capacity == N) | ||
{ | ||
_data = &_buffer[0]; | ||
_capacity = N; | ||
_size = other._size; | ||
// The earlier static_assert(std::is_nothrow_move_constructible_v<T>) | ||
// ensures that we don't exit in a weird state with invalid `_size`. | ||
#pragma warning(suppress : 26447) // The function is declared 'noexcept' but calls function '...' which may throw exceptions (f.6). | ||
std::uninitialized_move(other.begin(), other.end(), _uninitialized_begin()); | ||
std::destroy(other.begin(), other.end()); | ||
} |
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.
You're going to want to explain this. Why is it better to do it this way over just having the pointers always be moved into this one and calling it a day. I presume it's an optimization of some sort, but I don't know what it is. Comments?
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.
You mean why I check if (other._capacity == N)
here and do this extra branch?
If the other vector is "small" (= data is in the _buffer
array) its _capacity
will equal N
. But unlike a heap allocated pointer we can't just "move" a stack allocated array - we can only copy it. And that's why use std::uninitialized_move
here to create a copy of the other
vector's _buffer
.
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.
Ah yes yes. It's literally the entire optimization. Brain fart. Maybe just leave a small comment on the branch to help remind goofballs like me.
@lhecker are the TODOs to-done? |
@DHowett No I haven't worked on this yet. I was planning to continue with this PR once we forked off 1.16. |
@DHowett I've just pushed some final fixes and improvements and I think this PR is now ready to be merged. If possible I'd love if you could give the last commit another review. |
// This line is noexcept: | ||
// It can't throw because of the earlier static_assert(std::is_nothrow_move_assignable_v<T>). | ||
const auto displacement = std::min(count, moveable); | ||
std::uninitialized_move(end() - displacement, end(), _uninitialized_begin() + (new_size - displacement)); |
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.
do we have tests exercising this code? validating it?
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.
Yes, parts of the Basic tests cover this.
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 think i understand!
Hello @DHowett! Because this pull request has the p.s. you can customize the way I help with merging this pull request, such as holding this pull request until a specific person approves. Simply @mention me (
|
This replaces ~70k LOC (parts of boost, 1/4th of the code in this project)
with ~700 LOC (
small_vector.h
). By replacing boost, we simplify futuremaintenance and improve compile times.
Validation Steps Performed