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

Avoid double strlen for string operator+ and implement P1165R1 #467

Merged
merged 14 commits into from Feb 1, 2020

Conversation

@BillyONeal
Copy link
Member

BillyONeal commented Jan 28, 2020

This change is a competing proposal to GH-419.

Resolves GH-53.
Resolves GH-456.

This change adds a bespoke constructor to basic_string to handle string concat use cases, removing any EH states we previously emitted in our operator+s, avoiding double strlen in our operator+s,

The EH states problem comes from our old pattern:

S operator+(a, b) {
    S result;
    result.reserve(a.size() +b.size()); // throws
    result += a; // throws
    result += b; // throws
    return result;
}

Here, the compiler does not know that the append operation can't throw, because it doesn't understand basic_string and doesn't know the reserve has made that always safe. As a result, the compiler emitted EH handing code to call result's destructor after each of the reserve and operator+= calls.

Using a bespoke concatenating constructor avoids these problems because there is only one throwing operation (in IDL0 mode). As expected, this results in a small performance win in all concats due to avoiding needing to set up EH stuff, and a large performance win for the const char* concats due to the avoided second strlen:

Performance:

#include <benchmark/benchmark.h>
#include <stdint.h>
#include <string>

constexpr size_t big = 2 << 12;
constexpr size_t multiplier = 64;

static void string_concat_string(benchmark::State &state) {
    std::string x(static_cast<size_t>(state.range(0)), 'a');
    std::string y(static_cast<size_t>(state.range(1)), 'b');
    for (auto _ : state) {
        (void)_;
        benchmark::DoNotOptimize(x + y);
    }
}

BENCHMARK(string_concat_string)->RangeMultiplier(multiplier)->Ranges({{2, big}, {2, big}});

static void string_concat_ntbs(benchmark::State &state) {
    std::string x(static_cast<size_t>(state.range(0)), 'a');
    std::string yBuf(static_cast<size_t>(state.range(1)), 'b');
    const char *const y = yBuf.c_str();
    for (auto _ : state) {
        (void)_;
        benchmark::DoNotOptimize(x + y);
    }
}

BENCHMARK(string_concat_ntbs)->RangeMultiplier(multiplier)->Ranges({{2, big}, {2, big}});

static void string_concat_char(benchmark::State &state) {
    std::string x(static_cast<size_t>(state.range(0)), 'a');
    for (auto _ : state) {
        (void)_;
        benchmark::DoNotOptimize(x + 'b');
    }
}

BENCHMARK(string_concat_char)->Range(2, big);

static void ntbs_concat_string(benchmark::State &state) {
    std::string xBuf(static_cast<size_t>(state.range(0)), 'a');
    const char *const x = xBuf.c_str();
    std::string y(static_cast<size_t>(state.range(1)), 'b');
    for (auto _ : state) {
        (void)_;
        benchmark::DoNotOptimize(x + y);
    }
}

BENCHMARK(ntbs_concat_string)->RangeMultiplier(multiplier)->Ranges({{2, big}, {2, big}});

static void char_concat_string(benchmark::State &state) {
    std::string x(static_cast<size_t>(state.range(0)), 'a');
    for (auto _ : state) {
        (void)_;
        benchmark::DoNotOptimize('b' + x);
    }
}

BENCHMARK(char_concat_string)->Range(2, big);

BENCHMARK_MAIN();

Times are in NS on a Ryzen Threadripper 3970X, improvements are ((Old/New)-1)*100

old x64 new x64 improvement old x86 new x86 improvement
string_concat_string/2/2 12.8697 5.78125 122.61% 13.9029 11.0696 25.60%
string_concat_string/64/2 62.779 61.3839 2.27% 66.4394 61.6296 7.80%
string_concat_string/4096/2 125.558 124.512 0.84% 124.477 117.606 5.84%
string_concat_string/8192/2 188.337 184.152 2.27% 189.982 185.598 2.36%
string_concat_string/2/64 64.5229 64.1741 0.54% 67.1338 61.4962 9.17%
string_concat_string/64/64 65.5692 59.9888 9.30% 66.7742 60.4781 10.41%
string_concat_string/4096/64 122.768 122.768 0.00% 126.774 116.327 8.98%
string_concat_string/8192/64 190.43 181.362 5.00% 188.516 186.234 1.23%
string_concat_string/2/4096 125.558 119.978 4.65% 120.444 111.524 8.00%
string_concat_string/64/4096 125.558 119.978 4.65% 122.911 117.136 4.93%
string_concat_string/4096/4096 188.337 184.152 2.27% 193.337 182.357 6.02%
string_concat_string/8192/4096 273.438 266.811 2.48% 267.656 255.508 4.75%
string_concat_string/2/8192 205.078 194.964 5.19% 175.025 170.181 2.85%
string_concat_string/64/8192 205.078 188.337 8.89% 191.676 183.06 4.71%
string_concat_string/4096/8192 266.811 256.696 3.94% 267.455 255.221 4.79%
string_concat_string/8192/8192 414.69 435.965 -4.88% 412.784 403.01 2.43%
string_concat_ntbs/2/2 12.8348 5.9375 116.17% 14.74 11.132 32.41%
string_concat_ntbs/64/2 71.1496 59.375 19.83% 70.6934 60.9371 16.01%
string_concat_ntbs/4096/2 128.697 114.397 12.50% 126.626 121.887 3.89%
string_concat_ntbs/8192/2 194.964 176.479 10.47% 196.641 186.88 5.22%
string_concat_ntbs/2/64 100.446 74.986 33.95% 109.082 83.3939 30.80%
string_concat_ntbs/64/64 106.027 78.4738 35.11% 109.589 84.3635 29.90%
string_concat_ntbs/4096/64 164.969 138.114 19.44% 165.417 142.116 16.40%
string_concat_ntbs/8192/64 224.958 200.195 12.37% 228.769 200.347 14.19%
string_concat_ntbs/2/4096 2040.32 1074.22 89.94% 2877.33 1362.74 111.14%
string_concat_ntbs/64/4096 1994.98 1074.22 85.71% 2841.93 1481.62 91.81%
string_concat_ntbs/4096/4096 2050.78 1147.46 78.72% 2907.78 1550.82 87.50%
string_concat_ntbs/8192/4096 2148.44 1227.68 75.00% 2966.92 1583.78 87.33%
string_concat_ntbs/2/8192 3934.14 2099.61 87.37% 5563.32 2736.56 103.30%
string_concat_ntbs/64/8192 3989.95 1994.98 100.00% 5456.84 2823.53 93.26%
string_concat_ntbs/4096/8192 4049.24 2197.27 84.29% 5674.02 2957.04 91.88%
string_concat_ntbs/8192/8192 4237.58 2249.58 88.37% 5755.07 3095.65 85.91%
string_concat_char/2 12.8348 3.44936 272.09% 11.1104 10.6976 3.86%
string_concat_char/8 8.99833 3.45285 160.61% 11.1964 10.6928 4.71%
string_concat_char/64 65.5692 60.9375 7.60% 65.7585 60.0182 9.56%
string_concat_char/512 72.5446 69.7545 4.00% 83.952 79.5254 5.57%
string_concat_char/4096 125.558 119.978 4.65% 123.475 117.103 5.44%
string_concat_char/8192 190.43 187.988 1.30% 189.181 185.174 2.16%
ntbs_concat_string/2/2 13.4975 6.13839 119.89% 14.8623 11.09 34.02%
ntbs_concat_string/64/2 104.98 79.5201 32.02% 112.207 83.7111 34.04%
ntbs_concat_string/4096/2 2085.66 1098.63 89.84% 2815.19 1456.08 93.34%
ntbs_concat_string/8192/2 3899.27 2099.61 85.71% 5544.52 2765.16 100.51%
ntbs_concat_string/2/64 71.4983 62.779 13.89% 72.6602 63.1953 14.98%
ntbs_concat_string/64/64 104.98 80.2176 30.87% 111.073 81.8413 35.72%
ntbs_concat_string/4096/64 2085.66 1074.22 94.16% 2789.73 1318.7 111.55%
ntbs_concat_string/8192/64 3989.95 2085.66 91.30% 5486.85 2693.83 103.68%
ntbs_concat_string/2/4096 136.719 128.348 6.52% 122.605 114.44 7.13%
ntbs_concat_string/64/4096 167.411 142.997 17.07% 168.572 138.566 21.65%
ntbs_concat_string/4096/40 2099.61 1171.88 79.17% 2923.85 1539.02 89.98%
ntbs_concat_string/8192/40 4098.07 2246.09 82.45% 5669.34 3005.25 88.65%
ntbs_concat_string/2/8192 213.1 199.498 6.82% 178.197 168.532 5.73%
ntbs_concat_string/64/8192 223.214 214.844 3.90% 232.263 203.722 14.01%
ntbs_concat_string/4096/81 2148.44 1255.58 71.11% 2980.78 1612.97 84.80%
ntbs_concat_string/8192/81 4237.58 2406.53 76.09% 5775.55 3067.94 88.25%
char_concat_string/2 11.1607 3.60631 209.48% 11.2101 10.7192 4.58%
char_concat_string/8 11.4746 3.52958 225.10% 11.4595 10.709 7.01%
char_concat_string/64 65.5692 66.9643 -2.08% 66.6272 60.8601 9.48%
char_concat_string/512 68.0106 73.2422 -7.14% 91.1946 83.0791 9.77%
char_concat_string/4096 125.558 122.768 2.27% 119.432 110.031 8.54%
char_concat_string/8192 199.498 199.498 0.00% 171.895 169.173 1.61%

Code size:

#include <string>

std::string strings(const std::string& a, const std::string& b) {
    return a + b;
}
std::string string_ntbs(const std::string& a, const char * b) {
    return a + b;
}
std::string string_char(const std::string& a, char b) {
    return a + b;
}
std::string ntbs_string(const char * a, const std::string& b) {
    return a + b;
}
std::string char_string(char a, const std::string& b) {
    return a + b;
}

Sizes are in bytes for the .obj, "Times Original" is New/Old, cl /EHsc /W4 /WX /c /O2 .\code_size.cpp:

Bytes Before After Times Original
x64 70,290 34,192 0.486
x86 47,152 28,792 0.611

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 microsoft/vclibs as a code owner Jan 28, 2020
@BillyONeal BillyONeal self-assigned this Jan 28, 2020
@BillyONeal

This comment has been minimized.

Copy link
Member Author

BillyONeal commented Jan 28, 2020

…p\regress\objsize test.
@BillyONeal

This comment has been minimized.

Copy link
Member Author

BillyONeal commented Jan 28, 2020

Thanks for catching my number scramble @StephanTLavavej :)

Copy link
Member

StephanTLavavej left a comment

This approach looks correct to me, after resolving these issues.

explicit basic_string(_String_constructor_concat_tag, const basic_string& _Alsource, const _Elem* const _Left_ptr,
const size_type _Left_size, const _Elem* const _Right_ptr, const size_type _Right_size)
: _Mypair(
_One_then_variadic_args_t(), _Alty_traits::select_on_container_copy_construction(_Alsource._Getal())) {

This comment has been minimized.

Copy link
@StephanTLavavej

StephanTLavavej Jan 28, 2020

Member

We're currently inconsistent, but in new code, I'd like to use {} empty braces for tags like _One_then_variadic_args_t. I'll file an issue about cleaning up our sources; I don't think we have one yet.

This comment has been minimized.

Copy link
@BillyONeal

BillyONeal Jan 28, 2020

Author Member

Do you want me to fix up the tags in this file or just this one? (I'm not a fan of making it inconsistent with all the other ctors over this but if we're at least consistent within a file that'd be OK)

This comment has been minimized.

Copy link
@StephanTLavavej

StephanTLavavej Jan 28, 2020

Member

I filed #468 for the STL-wide overhaul, so I think I'd prefer to see parens used here for consistency and avoiding making this PR bigger.

@@ -2432,6 +2438,33 @@ public:
_Proxy._Release();
}

explicit basic_string(_String_constructor_concat_tag, const basic_string& _Alsource, const _Elem* const _Left_ptr,

This comment has been minimized.

Copy link
@StephanTLavavej

StephanTLavavej Jan 28, 2020

Member

The naming _Alsource is a little weird; we generally use _Almeow to refer to different flavors of allocators, but this is a basic_string serving as the source of an allocator. Any other naming would be fine, e.g. _Source_of_alloc or something.

const size_type _Left_size, const _Elem* const _Right_ptr, const size_type _Right_size)
: _Mypair(
_One_then_variadic_args_t(), _Alty_traits::select_on_container_copy_construction(_Alsource._Getal())) {
_STL_INTERNAL_CHECK(_Left_size < max_size());

This comment has been minimized.

Copy link
@StephanTLavavej

StephanTLavavej Jan 28, 2020

Member

I believe our usual convention is to permit sizes equal to max_size() as suggested by the name. It should be acceptable to concatenate maximum and zero. Note that your third check permits equality.

This comment has been minimized.

Copy link
@BillyONeal

BillyONeal Jan 28, 2020

Author Member

Hmm interesting. This is a bug in the _STL_INTERNAL_CHECK but not in the product and suggests I need to add zero-length appends to the test I added.

_STL_INTERNAL_CHECK(max_size() - _Left_size >= _Right_size);
const auto _New_size = _Left_size + _Right_size;
size_type _New_capacity = _BUF_SIZE - 1;
_Elem* _Ptr = _Mypair._Myval2._Bx._Buf;

This comment has been minimized.

Copy link
@StephanTLavavej

StephanTLavavej Jan 28, 2020

Member

You repeat _Mypair._Myval2 several times; I believe we usually factor this out into _My_data which is zero-overhead.

_One_then_variadic_args_t(), _Alty_traits::select_on_container_copy_construction(_Alsource._Getal())) {
_STL_INTERNAL_CHECK(_Left_size < max_size());
_STL_INTERNAL_CHECK(_Right_size < max_size());
_STL_INTERNAL_CHECK(max_size() - _Left_size >= _Right_size);

This comment has been minimized.

Copy link
@StephanTLavavej

StephanTLavavej Jan 28, 2020

Member

Style: I would prefer for all three comparisons to be <=; flipping the sense of the third one is more work to reason through.

This comment has been minimized.

Copy link
@BillyONeal

BillyONeal Jan 28, 2020

Author Member

Right, I didn't try to make them consistent because I was using < for some and <= for others, but as you point out in the other comment, that was incorrect. I'll make them consistent.

_Xlen_string();
}

return basic_string<_Elem, _Traits, _Alloc>(

This comment has been minimized.

Copy link
@StephanTLavavej

StephanTLavavej Jan 28, 2020

Member

You can use _String_type here.

This comment has been minimized.

Copy link
@BillyONeal

BillyONeal Jan 28, 2020

Author Member

Actually going to eliminate string_type and apply the de-explicit you suggested in the other comment.

_Xlen_string();
}

return basic_string<_Elem, _Traits, _Alloc>(

This comment has been minimized.

Copy link
@StephanTLavavej

StephanTLavavej Jan 28, 2020

Member

_String_type again.

@@ -2195,6 +2195,12 @@ public:
template <class _Ty>
constexpr size_t _Size_after_ebco_v = is_empty_v<_Ty> ? 0 : sizeof(_Ty); // get _Ty's size after being EBCO'd

struct _String_constructor_concat_tag {};

This comment has been minimized.

Copy link
@StephanTLavavej

StephanTLavavej Jan 28, 2020

Member

Idea: I observe that you've made this implicitly default constructible, while the basic_string ctor is explicit. What if you gave this tag an explicit default constructor (as some tags in the Standard do), forbidding {} from being used to construct the tag, and then made the basic_string ctor implicit? That would allow you to say return { _String_constructor_concat_tag{}, ARGS } below, while not seriously affecting safety in any way. Feel free to disregard though.

This comment has been minimized.

Copy link
@BillyONeal

BillyONeal Jan 28, 2020

Author Member

I made the basic_string ctor explicit because I want to be as paranoid as possible about conversion sequences affecting basic_string's public interface, and I dislike giving these tags ctors because our ABI won't pass such a tag in a register. However, it looks like if we make an inline constexpr variable in release mode that gets eaten so that's OK: https://gcc.godbolt.org/z/jAU-cr

Hmmm and for some reason it looks like in debug mode that's actually an improvement: I find that surprising. https://gcc.godbolt.org/z/0IHz4F Maybe we should start doing this for all tags (including one_then_variadic_args and friends)?

This comment has been minimized.

Copy link
@BillyONeal

BillyONeal Jan 28, 2020

Author Member

(That is to say, my initial reaction was "I don't want to do that because codegen", then I actually checked the codegen, and now my reaction is "I want to do that because codegen" :)

This comment has been minimized.

Copy link
@StephanTLavavej

StephanTLavavej Jan 28, 2020

Member

Can you add that suggestion (inline constexpr) to #468 and explain your codegen discovery? We didn't have inline constexpr back when we implemented the old tags. (Also, we should check whether C++14 without inline variables tolerates this as well.)

This comment has been minimized.

Copy link
@BillyONeal

BillyONeal Jan 28, 2020

Author Member

Will do

This comment has been minimized.

Copy link
@CaseyCarter

CaseyCarter Jan 28, 2020

Member

However, it looks like if we make an inline constexpr variable in release mode that gets eaten so that's OK

You need to uncheck the ".LX0" and ".text" filters in CE to see the definition of the variable; it's not emitted in the non-USE_INLINE cases since it isn't ODR-used, but is emitted for /DUSE_INLINE. The compiler apparently doesn't realize that the object representation is only padding bits that it need not copy when making a copy of the variable; it copies a byte of uninitialized stack space for non-USE_INLINE, and copies the representation tagInstance for USE_INLINE.

This comment has been minimized.

Copy link
@BillyONeal

BillyONeal Jan 29, 2020

Author Member

Ah, Casey is right. In that case release codegen for creating the temporary (what we already do) is best.

_Ans += _Left;
_Ans += _Right;
return _Ans;
if (_Left.max_size() - _Left.size() < _Right.size()) {

This comment has been minimized.

Copy link
@StephanTLavavej

StephanTLavavej Jan 28, 2020

Member

Debug codegen question for all operators: Should you factor out the calls to each .size() since they are repeated in both the overflow check and the actual construction?

This comment has been minimized.

Copy link
@BillyONeal

BillyONeal Jan 28, 2020

Author Member

Who are you and what did you do with Stephan?

@BillyONeal

This comment has been minimized.

Copy link
Member Author

BillyONeal commented Jan 28, 2020

I wish I had a better way to put benchmark results here than screenshots as they aren't great for anyone but fully sighted users, consider this a vote for google/benchmark#441

@miscco

This comment has been minimized.

Copy link
Contributor

miscco commented Jan 28, 2020

There are currently multiple PRs for libc++ that try to optimize the SSO case of the basic_string contructors through a tail-call optimization e.g. for the copy constructor.

I have no idea how the SSO case is currently handeled in the STL machinery, but it might be worth it to consider that kind of optimization when touching all those operators anyway

@BillyONeal

This comment has been minimized.

Copy link
Member Author

BillyONeal commented Jan 28, 2020

There are currently multiple PRs for libc++ that try to optimize the SSO case of the basic_string contructors through a tail-call optimization e.g. for the copy constructor.

I have no idea how the SSO case is currently handeled in the STL machinery, but it might be worth it to consider that kind of optimization when touching all those operators anyway

I think this sounds like a separate consideration and we would want a pattern to fix all the constructors to follow probably. It should be noted that we are different than libc++ in a major respect here; namely, that we can reuse the initialization code between our small and large representations, whereas for the most part they might find that difficult because their layouts are totally different in the different modes. If someone wanted to investigate that area I think they would want to start with the

if (_Right_size < _BUF_SIZE) { // stay small, don't allocate
family.

I do note that for copies we do do the "just copy over the entire string data structure" optimization; see 19-28 here: https://gcc.godbolt.org/z/hP9jo9

@BillyONeal

This comment has been minimized.

Copy link
Member Author

BillyONeal commented Jan 28, 2020

There are currently multiple PRs for libc++ that try to optimize the SSO case of the basic_string contructors through a tail-call optimization e.g. for the copy constructor.

I have no idea how the SSO case is currently handeled in the STL machinery, but it might be worth it to consider that kind of optimization when touching all those operators anyway

Filed #472

…stently use <= and fix <= that was <, extract _My_data in the ctor, add throws annotation to _Proxy construction, memoize size.
stl/inc/xstring Show resolved Hide resolved
stl/inc/xstring Show resolved Hide resolved
stl/inc/xstring Outdated Show resolved Hide resolved
stl/inc/xstring Outdated Show resolved Hide resolved
stl/inc/xstring Outdated Show resolved Hide resolved
@StephanTLavavej StephanTLavavej self-requested a review Jan 30, 2020
stl/inc/xstring Outdated Show resolved Hide resolved
stl/inc/xstring Outdated Show resolved Hide resolved
stl/inc/xstring Outdated Show resolved Hide resolved
stl/inc/xstring Outdated Show resolved Hide resolved
stl/inc/xstring Outdated Show resolved Hide resolved
stl/inc/xstring Outdated Show resolved Hide resolved
…ffer" branch.

* Turn on alias check for CONTAINER_DEBUG_LEVEL rather than ITERATOR_DEBUG_LEVEL
* Fix off-by-1 error in capacity check when attempting to use left buffer.
stl/inc/xstring Outdated Show resolved Hide resolved
stl/inc/xstring Outdated Show resolved Hide resolved
stl/inc/xstring Outdated Show resolved Hide resolved
stl/inc/xstring Outdated Show resolved Hide resolved
stl/inc/xstring Outdated Show resolved Hide resolved
stl/inc/xstring Outdated Show resolved Hide resolved
stl/inc/xstring Outdated Show resolved Hide resolved
_My_data._Alloc_proxy(_GET_PROXY_ALLOCATOR(_Alty, _Getal())); // throws, hereafter nothrow in this block
_Take_contents(_Right, bool_constant<_Can_memcpy_val>{});
const auto _Ptr = _Unfancy(_My_data._Bx._Ptr);
_Traits::move(_Ptr + _Left_size, _Ptr, _My_data._Mysize);

This comment has been minimized.

Copy link
@StephanTLavavej

StephanTLavavej Jan 31, 2020

Member

Same comment about avoiding a null terminator assignment at the end - you could simply move down the freshly taken null terminator here. This will also improve locality a little, hopefully.

This comment has been minimized.

Copy link
@BillyONeal

BillyONeal Jan 31, 2020

Author Member

Shouldn't improve locality at all; after all we just wrote the character before that null terminator so that cache location is hot.

This comment has been minimized.

Copy link
@StephanTLavavej

StephanTLavavej Jan 31, 2020

Member

You were performing the following actions: take right's buffer, move down right's data (without the null terminator), copy in left's data to the beginning of the buffer, then go back to the end of the buffer and write the null terminator. If you had scribbled the null terminator immediately after moving down right's data, then I would agree that there is no locality change.

This comment has been minimized.

Copy link
@BillyONeal

BillyONeal Jan 31, 2020

Author Member

Ah, I see, because this is the move around case. OK.

stl/inc/xstring Outdated Show resolved Hide resolved
* Introduce _Fits_in_left and _Fits_in_right which clarify proof block
* Use +1 instead of explicit null termination
* Reuse _Left_size and _Right_size more
* Go back to IDL for alias check.
@BillyONeal BillyONeal requested a review from StephanTLavavej Jan 31, 2020
_NODISCARD constexpr bool _Allocators_equal(const _Alloc& _Lhs, const _Alloc& _Rhs) noexcept {
if
_CONSTEXPR_IF(allocator_traits<_Alloc>::is_always_equal::value) {
(void) _Lhs;

This comment has been minimized.

Copy link
@barcharcraz

barcharcraz Jan 31, 2020

Contributor

why do we need to cast these to void here? is it just because the compiler will complain about them being unused without realizing that they are used in the other branch of the constexpr if?

This comment has been minimized.

Copy link
@StephanTLavavej

StephanTLavavej Jan 31, 2020

Member

Yes, this is (Microsoft-internal) VSO-486357, which is so frequently encountered that we don't mark every occurrence as TRANSITION (although we could, I suppose).

This comment has been minimized.

Copy link
@BillyONeal

BillyONeal Jan 31, 2020

Author Member

I would prefer an STL-wide update like tagging all of them be its own change.

This comment has been minimized.

Copy link
@miscco

miscco Feb 1, 2020

Contributor

I think the whole purpose of if constexpr is that the other branches are not processed by the Compiler. Adding scanning for uses of such variables defeats its purpose

This comment has been minimized.

Copy link
@StephanTLavavej

StephanTLavavej Feb 1, 2020

Member

They still have to be parsed (it’s not “token soup”) and other compilers avoid warning in this scenario.

@@ -2195,6 +2195,14 @@ public:
template <class _Ty>
constexpr size_t _Size_after_ebco_v = is_empty_v<_Ty> ? 0 : sizeof(_Ty); // get _Ty's size after being EBCO'd

struct _String_constructor_concat_tag {

This comment has been minimized.

Copy link
@barcharcraz

barcharcraz Jan 31, 2020

Contributor

I'd like a comment saying what this tag does.

Something like:

// used in operator+ when we've already computed the size, to avoid another strlen check

(not sure if that's a 100% accurate description of what it does)

// therefore: (by the distributive property)
// (!_Fits_in_left && _Fits_in_right) // implying _Right has more capacity
// || (_Right_capacity > _Left_capacity && _Fits_in_right) // tests that _Right has more capacity
// therefore: _Right must have more than the minimum capacity, so it must be _Large_string_engaged()

This comment has been minimized.

Copy link
@StephanTLavavej

StephanTLavavej Jan 31, 2020

Member

This explanation is very clear and easy to follow, thank you!

This comment has been minimized.

Copy link
@BillyONeal

BillyONeal Jan 31, 2020

Author Member

Glad you like it :)

_My_data._Alloc_proxy(_GET_PROXY_ALLOCATOR(_Alty, _Getal())); // throws, hereafter nothrow in this block
_Take_contents(_Right, bool_constant<_Can_memcpy_val>{});
const auto _Ptr = _Unfancy(_My_data._Bx._Ptr);
_Traits::move(_Ptr + _Left_size, _Ptr, _My_data._Mysize + 1);

This comment has been minimized.

Copy link
@StephanTLavavej

StephanTLavavej Jan 31, 2020

Member

Apologies for not noticing earlier - _My_data._Mysize can/should be _Right_size after taking the contents of _Right.

This comment has been minimized.

Copy link
@BillyONeal

BillyONeal Jan 31, 2020

Author Member

No need to apologize -- I thought I got all of them but I guess I missed one.

@BillyONeal BillyONeal requested a review from StephanTLavavej Jan 31, 2020
@BillyONeal BillyONeal merged commit 94d39ed into microsoft:master Feb 1, 2020
6 checks passed
6 checks passed
license/cla All CLA requirements met.
Details
microsoft.STL Build #20200131.7 succeeded
Details
microsoft.STL (arm) arm succeeded
Details
microsoft.STL (arm64) arm64 succeeded
Details
microsoft.STL (x64) x64 succeeded
Details
microsoft.STL (x86) x86 succeeded
Details
@BillyONeal BillyONeal deleted the BillyONeal:optimize_string_concat branch Feb 1, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
5 participants
You can’t perform that action at this time.