-
Notifications
You must be signed in to change notification settings - Fork 1.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
Reduce stack space consumption of list<T>::insert #366
Conversation
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 interested if there are much better ideas that avoid a branch on every iteration or the increased code size from calling the ctor twice.
_Alloc_construct_ptr<_Alnode> _Newnode(_Al); | ||
if (_Tail == pointer{}) { | ||
_Newnode._Allocate(); // throws | ||
_Alnode_traits::construct(_Al, _STD addressof(_Newnode._Ptr->_Myval), _Carg...); // throws |
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 repeated constructor has a somewhat negative code size cost.
const auto _Newhead = _Newnode._Release(); | ||
_Head = _Newhead; | ||
_Tail = _Newhead; | ||
++_First; // throws |
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.
Previously we forgot to handle an iterator that threw in its op++ nicely.
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.
Using operator++ throws and nicely in a sentence...
_Alloc_construct_ptr<_Alnode> _Newnode(_Al); | ||
if (_Added == 0) { | ||
_Newnode._Allocate(); // throws | ||
_Alnode_traits::construct(_Al, _STD addressof(_Newnode._Ptr->_Myval), _Carg...); // throws |
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.
Ditto unfortunate code duplication
if (_Added == 0) { | ||
_Newnode._Allocate(); // throws; | ||
_Alnode_traits::construct(_Al, _STD addressof(_Newnode._Ptr->_Myval), *_First); // throws | ||
const auto _Newhead = _STD exchange(_Newnode._Ptr, pointer{}); |
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.
The null assignment moved into the loop to handle throwing _First++.
stl/inc/list
Outdated
_List_node_insert_op(const _List_node_insert_op&) = delete; | ||
_List_node_insert_op& operator=(const _List_node_insert_op&) = delete; | ||
_List_node_insert_op2(const _List_node_insert_op2&) = delete; | ||
_List_node_insert_op2& operator=(const _List_node_insert_op2&) = delete; | ||
|
||
template <class... _CArgT> | ||
void _Append_n(size_type _Count, const _CArgT&... _Carg) { | ||
// Append _Count Ts constructed from _Carg to this insert operation. |
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, but we usually avoid saying "Ts" since nothing is actually named T in this implementation. Can this say "elements"?
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.
How about "values"? (Since containers have value_type, not element_type)
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.
We always speak of containers having elements. The distinction is critical, beyond the syntax of value_type
. The difference is that with a list<int>
, an int
value might be provided (e.g. in push_back
), but an int element
is physically owned by the container.
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.
Technically they aren't owned by a container yet here but they will be soon :). Will use elements.
stl/inc/list
Outdated
_Alloc_construct_ptr<_Alnode> _Newnode(_Al); | ||
for (; _First != _Last; ++_First) { | ||
if (_Added == 0) { | ||
_Newnode._Allocate(); // throws; |
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.
Spurious semicolon in comment.
stl/inc/list
Outdated
_Construct_in_place(_Newnode._Ptr->_Prev, _Local_tail); | ||
_Construct_in_place(_Local_head->_Prev, _Newnode._Ptr); | ||
_Construct_in_place(_Local_tail->_Next, _Newnode._Ptr); | ||
_My_data._Mysize = _Local_added; |
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.
Because this is "assumed nothrow hereafter", I observe that _My_data._Mysize = 0;
on 724 and _My_data._Mysize = _Local_added;
here could be extracted.
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, in this case it's no longer an assumption because we are no longer doing allocator::construct on pointers. And yes, will extract.
stl/inc/forward_list
Outdated
|
||
template <class... _CArgT> | ||
pointer _Append_n(typename _Alnode_traits::size_type _Count, const _CArgT&... _Carg) { | ||
void _Append_n(typename _Alnode_traits::size_type _Count, const _CArgT&... _Carg) { | ||
// Append _Count copies of T, constructed from _Carg, to this insert-after operation |
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, can we avoid saying "T" when there is no such name?
stl/inc/forward_list
Outdated
} | ||
|
||
void _Attach_after(pointer _After) noexcept { | ||
pointer _Attach_after(pointer _After) noexcept { | ||
// Attaches the values in this insert operation after _After, and resets *this to the default initialized state |
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, hyphenate "default-initialized"?
stl/inc/forward_list
Outdated
// Attaches the values in this insert operation after _After, and resets *this to the default initialized state | ||
auto _Local_tail = _Tail; |
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.
Can _Local_tail
be const?
_Newnode._Allocate(); // throws | ||
_Alnode_traits::construct(_Al, _STD addressof(_Newnode._Ptr->_Myval), *_First); // throws | ||
_Construct_in_place(*_Tail, _Newnode._Ptr); // nothrow | ||
_Tail = _STD addressof(_Newnode._Ptr->_Next); | ||
const auto _Newhead = _Newnode._Release(); |
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.
There is an implementation divergence between _Append_n
and this function in that additional temporary. I could not find the reason for that.
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.
--_Count can't throw, so we can avoid a bunch of nullptr assignments, but ++_First can throw, so we need to deactivate the _Alloc_construct_ptr
after ownership of each element has been transferred to *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.
Thanks for the clarification. We need a “that is correct and I am deeply horrified“ emoji
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.
The eyes are close enough right?
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.
Nah, depending on the browser they can look cute. 😱 seems appropriate
stl/inc/list
Outdated
return _First_inserted; | ||
_My_data._Mysize += _Local_added; | ||
_Added = 0; | ||
_Head = pointer{}; |
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.
Don't need to zero these out since _Added == 0 already marks it.
Description
This change fixes _List_node_insert_op to avoid consuming otherwise unused stack space for a T.
Checklist
Be sure you've read README.md and understand the scope of this repo.
If you're unsure about a box, leave it unchecked. A maintainer will help you.
_Ugly
as perhttps://eel.is/c++draft/lex.name#3.1 or there are no product code changes.
verified by an STL maintainer before automated testing is enabled on GitHub,
leave this unchecked for initial submission).
members, adding virtual functions, changing whether a type is an aggregate
or trivially copyable, etc.).
the C++ Working Draft (including any cited standards), other WG21 papers
(excluding reference implementations outside of proposed standard wording),
and LWG issues as reference material. If they were derived from a project
that's already listed in NOTICE.txt, that's fine, but please mention it.
If they were derived from any other project (including Boost and libc++,
which are not yet listed in NOTICE.txt), you must mention it here,
so we can determine whether the license is compatible and what else needs
to be done.