Skip to content
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

Merged
merged 3 commits into from
Dec 11, 2019

Conversation

BillyONeal
Copy link
Member

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.

  • Identifiers in product code changes are properly _Ugly as per
    https://eel.is/c++draft/lex.name#3.1 or there are no product code changes.
  • The STL builds successfully and all tests have passed (must be manually
    verified by an STL maintainer before automated testing is enabled on GitHub,
    leave this unchecked for initial submission).
  • These changes introduce no known ABI breaks (adding members, renaming
    members, adding virtual functions, changing whether a type is an aggregate
    or trivially copyable, etc.).
  • These changes were written from scratch using only this repository,
    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.

@BillyONeal BillyONeal requested a review from a team as a code owner December 10, 2019 04:29
Copy link
Member Author

@BillyONeal BillyONeal left a 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
Copy link
Member Author

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
Copy link
Member Author

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.

Copy link
Contributor

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
Copy link
Member Author

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{});
Copy link
Member Author

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.
Copy link
Member

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"?

Copy link
Member Author

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)

Copy link
Member

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.

Copy link
Member Author

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;
Copy link
Member

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;
Copy link
Member

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.

Copy link
Member Author

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.


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
Copy link
Member

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?

}

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
Copy link
Member

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"?

// Attaches the values in this insert operation after _After, and resets *this to the default initialized state
auto _Local_tail = _Tail;
Copy link
Member

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();
Copy link
Contributor

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.

Copy link
Member Author

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.

Copy link
Contributor

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

Copy link
Member Author

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?

Copy link
Contributor

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{};
Copy link
Member Author

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.

@BillyONeal BillyONeal merged commit 991ffe5 into microsoft:master Dec 11, 2019
@BillyONeal BillyONeal deleted the stack_space branch December 11, 2019 20:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants