为啥按值传递 string_view?为啥 Visual Studio 不能对此进行优化?
Posted
技术标签:
【中文标题】为啥按值传递 string_view?为啥 Visual Studio 不能对此进行优化?【英文标题】:Why pass string_view by value? And why can't Visual Studio optimize this?为什么按值传递 string_view?为什么 Visual Studio 不能对此进行优化? 【发布时间】:2018-11-13 11:59:00 【问题描述】:根据我的直觉,我假设新的 string_view 需要通过引用传递,因为这样更有效(只传递指针而不是完整的类)。但是,一些消息来源表明最好按值传递,避免“混叠”问题。
C++ view types: pass by const& or by value? https://abseil.io/tips/1在尝试几种替代方案时,我确认了我的直觉,如果该函数只是转发 string_view(所有源代码均使用 /Ox
编译),那么通过引用传递会更快。
比如这段代码
extern auto otherMethodByReference(const std::string_view &input) -> void;
auto thisMethodByReference(int value, const std::string_view &input) -> void
otherMethodByReference(input);
导致了这个程序集
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
这段代码
extern auto otherMethodByValue(std::string_view input) -> void;
auto thisMethodByValue(int value, std::string_view input) -> void
otherMethodByValue(input);
导致了这个程序集
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
很明显,您可以看到在堆栈上创建了 string_view 的副本,然后传递给另一个方法。
但是,我想知道,为什么编译器不对此进行优化,而只是将 string_view 参数直接传递给另一个方法。毕竟,在 Windows x64 ABI 中,一个大于寄存器的类的值传递总是通过复制堆栈上的寄存器,并在正确的寄存器中传递一个指向它的指针来完成。我希望在这个示例代码中编译器会简单地将指针转发到下一个函数,就像在传递引用的情况下一样。毕竟编译器可以看到参数的值后面没有用到,所以不用复制,直接转发地址就好了。
我尝试将 std::move 添加到调用中,如下所示:
auto thisMethodByValueAndMove(int value, std::string_view input) -> void
otherMethodByValue(std::move(input));
但这似乎没有帮助。
Visual Studio 2017 编译器无法对此进行优化是否有原因? 其他编译器是否优化了这种模式?
【问题讨论】:
gcc 和 clang 为 pass-by-value 添加了一个额外的mv
指令。
如果您不需要在函数中使用string_view
对象本身,只需将其传递,那么将其作为参考传递可能是有意义的,尤其是在您完成研究之后。但是如果您访问string_view
对象,请记住传递引用会增加间接性。还有两个你没有测试过的用例:一个thisMethodByReference
调用otherMethodByValue
;而另一个则相反。
Do other compilers optimize this pattern?
- 测试Godbolt 我猜。 MSVC 显然在这里太“受挑战”了,let them know。
@Paul,谢谢,刚刚测试了 Godbolt。 Clang 确实将此优化为 3 条指令(2 次移动寄存器和 1 次跳转(尾调用)。所以这确实是微软的问题。
来自 Godbolt 的“分享”并发布链接?
【参考方案1】:
X64 调用约定不允许在不同的寄存器中传播参数。 编译器可以通过 rcx 和 rdx 传递 string_view 但 ABI 反对这一点。 https://docs.microsoft.com/en-us/cpp/build/x64-calling-convention?view=vs-2019
【讨论】:
我知道,但这不是重点。如果您通过引用传递 string_view,则指向 string_view 的指针会在寄存器中传递,如果然后将其转发给另一个方法,则只会转发指针。如果您按值传递 string_view,它将被复制到堆栈上,并且堆栈上指向该值的指针也会在寄存器中传递。如果然后转发它,它似乎会制作 string_view 的完整副本,而不是像通过引用传递时那样复制指针。 Clang 似乎很好地优化了这一点,MSVC 没有。 没错。另一方面,它可以进行其他优化。例如,两个按值传递的 string_view 对象总是不同的对象,而两个引用可能指向同一个对象。以上是关于为啥按值传递 string_view?为啥 Visual Studio 不能对此进行优化?的主要内容,如果未能解决你的问题,请参考以下文章
为啥 std::vector::resize(n, src) 按值传递?