6

Using my gut feeling I assumed by the new string_view needed to be passed by reference, since this is more efficient (only pass pointer instead of a full class). However, several sources indicate that it's better to pass it by value, avoiding the 'aliasing' problem.

When trying out several alternatives, I confirmed my gut feeling that passing by reference was faster, if the function did nothing more than just forwarding the string_view (all sources compiled with /Ox)

For example, this code

extern auto otherMethodByReference(const std::string_view &input) -> void;
auto thisMethodByReference(int value, const std::string_view &input) -> void
   {
   otherMethodByReference(input);
   }

Resulted in this assembly

00000   48 8b ca     mov     rcx, rdx
00003   e9 00 00 00 00   jmp     ?otherMethodByReference@@YAXAEBV?$basic_string_view@DU?$char_traits@D@std@@@std@@@Z ; otherMethodByReference

While this code

extern auto otherMethodByValue(std::string_view input) -> void;
auto thisMethodByValue(int value, std::string_view input) -> void
   {
   otherMethodByValue(input);
   }

Resulted in this assembly

00000   48 83 ec 38  sub     rsp, 56            ; 00000038H
00004   0f 10 02     movups  xmm0, XMMWORD PTR [rdx]
00007   48 8d 4c 24 20   lea     rcx, QWORD PTR $T1[rsp]
0000c   0f 29 44 24 20   movaps  XMMWORD PTR $T1[rsp], xmm0
00011   e8 00 00 00 00   call    ?otherMethodByValue@@YAXV?$basic_string_view@DU?$char_traits@D@std@@@std@@@Z ; otherMethodByValue
00016   48 83 c4 38  add     rsp, 56            ; 00000038H
0001a   c3       ret     0

Clearly you can see that a copy of the string_view is made on the stack and then passed to the other method.

However, I was wondering, why doesn't the compiler optimize this and simply pass the string_view argument directly to the other method. After all, in the Windows x64 ABI a pass-by-value of a class larger than what fits in a register is always done by copying the register on the stack, and passing a pointer to it in the correct register. I would expect in this example code that the compiler would simply forward the pointer to the next function, just like in the pass-by-reference case. After all, the compiler can see that the value of the argument is not used afterwards, so instead of making a copy, it can just forward the address.

I tried adding std::move to the call, like this:

auto thisMethodByValueAndMove(int value, std::string_view input) -> void
   {
   otherMethodByValue(std::move(input));
   }

But that doesn't seem to help.

Is there a reason why the Visual Studio 2017 compiler cannot optimize this? Do other compilers optimize this pattern?

Patrick
  • 23,217
  • 12
  • 67
  • 130
  • gcc and clang add an extra `mv` instruction for the pass-by-value. – Holt Jun 04 '18 at 07:51
  • If you do not need to use the `string_view` object itself in the function, just passing it on, then it might make sense to pass it as a reference, especially after the research you have done. But if you access the `string_view` object, then remember that passing a reference adds indirection. There are also two use-cases you have not tested: One where `thisMethodByReference` calls `otherMethodByValue`; And the other being the opposite. – Some programmer dude Jun 04 '18 at 07:59
  • 1
    `Do other compilers optimize this pattern?` - test at [Godbolt](https://godbolt.org/) I guess. MSVC is evidently too 'challenged' here, [let them know](https://learn.microsoft.com/en-us/visualstudio/ide/how-to-report-a-problem-with-visual-studio-2017). – Paul Sanders Jun 04 '18 at 08:00
  • @Paul, thanks, just tested Godbolt. Clang indeed optimizes this to 3 instructions (2 moves of registers and 1 jump (tail-call). So it indeed seems a Microsoft problem. – Patrick Jun 04 '18 at 08:03
  • 'Share' from Godbolt and post link? – Paul Sanders Jun 04 '18 at 08:07
  • @Someprogrammerdude Passing by reference explicitly and having the compiler (hopefully) do the same thing on your behalf should generate the same code, shouldn't it? Sorry, I'm too lazy to test this at Godbolt. – Paul Sanders Jun 04 '18 at 08:12
  • https://godbolt.org/g/KKoNDM – Patrick Jun 04 '18 at 08:17
  • Logged at Microsoft: https://visualstudio.uservoice.com/forums/121579-visual-studio-ide/suggestions/34442251-forwarding-large-classes-by-value-not-correctly-op – Patrick Jun 04 '18 at 08:18
  • @Patrick Wouldn't "tail call optimization" be more suitable term in this case? Also have you ever managed to get such issues resolved through ms feedback websites? They tend to ignore severe performance issues for decades. – user7860670 Jun 04 '18 at 08:39
  • @VTT: This is the second suggestion I logged at Microsoft. The first one (increase the PDB size limit from 1 GB to at least 4 GB) took a while, but finally they increased the limit (probably because too many people complained about it). – Patrick Jun 04 '18 at 11:38

1 Answers1

0

X64 calling convention doesn't allow spreading an argument across different registers. Compiler could pass string_view via rcx and rdx but ABI is against this. https://learn.microsoft.com/en-us/cpp/build/x64-calling-convention?view=vs-2019

NN_
  • 1,593
  • 1
  • 10
  • 26
  • I know, but that was not the point. If you pass a string_view by reference, a pointer to the string_view is passed in a register and if then forward this to another method, the pointer is just forwarded. If you pass a string_view by value, it is copied on the stack, and also a pointer to this value on the stack is passed in a register. If you then forward it, it seems to make a complete copy of the string_view, rather than just copying the pointer like is done when passing by reference. Clang seems to decently optimize this, MSVC doesn't. – Patrick Jul 10 '19 at 06:39
  • That's correct. On the other hand it makes possible to do other optimizations. For instance two string_view objects passed by value always be different objects, while two references might point to the same object. – NN_ Jul 10 '19 at 12:04