gpt4 book ai didi

c++ - Visual C++ 优化选项 - 如何改进代码输出?

转载 作者:行者123 更新时间:2023-12-01 18:26:33 25 4
gpt4 key购买 nike

是否有任何选项(/O2 除外)来改进 Visual C++ 代码输出? MSDN 文档在这方面非常糟糕。
请注意,我不是在询问项目范围的设置(链接时间优化等)。我只对这个特定的例子感兴趣。

相当简单的 C++11 代码如下所示:

#include <vector>
int main() {
std::vector<int> v = {1, 2, 3, 4};
int sum = 0;
for(int i = 0; i < v.size(); i++) {
sum += v[i];
}
return sum;
}

Clang 的 libc++ 输出非常紧凑:
main: # @main
mov eax, 10
ret

另一方面,Visual C++ 输出是多页的困惑。
我在这里遗漏了什么还是 VS 真的这么糟糕?

编译器资源管理器链接:
https://godbolt.org/g/GJYHjE

最佳答案

不幸的是,在这种情况下,即使使用更积极的优化标志,也很难大幅改善 Visual C++ 的输出。导致 VS 效率低下的因素有很多,包括缺少某些编译器优化,以及 Microsoft 实现 <vector> 的结构。 .
检查生成的程序集,Clang 在优化此代码方面做得非常出色。具体来说,与 VS 相比,Clang 能够执行非常有效的常量传播、函数内联(以及因此的死代码消除)和新建/删除优化。
恒定传播
在示例中, vector 被静态初始化:

std::vector<int> v = {1, 2, 3, 4};
正常情况下,编译器会将常量1、2、3、4存放在数据内存中,在for循环中,会一次加载一个值,从存放1的低地址开始,将每个值相加值的总和。
这是用于执行此操作的缩写 VS 代码:
movdqa   xmm0, XMMWORD PTR __xmm@00000004000000030000000200000001
...
movdqu XMMWORD PTR $T1[rsp], xmm0 ; Store integers 1, 2, 3, 4 in memory
...
$LL4@main:
add ebx, DWORD PTR [rdx] ; loop and sum the values
lea rdx, QWORD PTR [rdx+4]
inc r8d
movsxd rax, r8d
cmp rax, r9
jb SHORT $LL4@main
然而,Clang 非常聪明地意识到可以提前计算总和。我最好的猜测是,它将常量从内存加载到常量 mov 操作替换为寄存器(传播常量),然后将它们组合成 10 的结果。这具有打破依赖关系的有用副作用,并且由于地址不再加载,编译器可以自由地将其他所有内容作为死代码删除。
Clang 在这方面似乎是独一无二的 - VS 或 GCC 都无法提前预先计算 vector 累积结果。
新增/删除优化
符合 C++14 的编译器允许在某些条件下省略对 new 和 delete 的调用,特别是当分配调用的数量不是程序可观察行为的一部分时( N3664 标准论文)。
这已经引起了很多关于 SO 的讨论:
  • clang vs gcc - optimization including operator new
  • Is the compiler allowed to optimize out heap memory allocations?
  • Optimization of raw new[]/delete[] vs std::vector

  • Clang 调用 -std=c++14 -stdlib=libc++确实执行了这种优化并消除了对 new 和 delete 的调用,它们确实带有副作用,但据说不会影响程序的可观察行为。与 -stdlib=libstdc++ , Clang 更严格并保留对 new 和 delete 的调用 - 尽管通过查看程序集,很明显它们并不是真正需要的。
    现在,在检查 main 时由 VS 生成的代码,我们可以找到两个函数调用(其余的 vector 构造和迭代代码内联到 main 中):
    call std::vector<int,std::allocator<int> >::_Range_construct_or_tidy<int const * __ptr64>
    call void __cdecl operator delete(void * __ptr64)
    第一个用于分配 vector ,第二个用于释放它,实际上 VS 输出中的所有其他函数都被这些函数调用拉入。这暗示 Visual C++ 不会优化掉对分配函数的调用(为了符合 C++14,我们应该添加 /std:c++14 标志,但结果是相同的)。
    blog post (2017 年 5 月 10 日)来自 Visual C++ 团队的确认确实没有实现此优化。搜索页面 N3664显示“避免/融合分配”处于 N/A 状态,链接评论说:

    [E] Avoiding/fusing allocations is permitted but not required. For the time being, we’ve chosen not to implement this.


    结合新/删除优化和常量传播,很容易在这个 Compiler Explorer 中看到这两种优化的影响。 Clang 与 -stdlib=libc++ 的 3 向比较, 叮当与 -stdlib=libstdc++ ,和海湾合作委员会。
    STL 实现
    VS 有自己的 STL 实现,它的结构与 libc++ 和 stdlibc++ 非常不同,这似乎对 VS 劣质代码生成有很大贡献。虽然 VS STL 有一些非常有用的特性,比如检查迭代器和迭代器调试钩子(Hook) ( _ITERATOR_DEBUG_LEVEL),但它给人的总体印象是比 stdlibc++ 更重且执行效率更低。
    为了隔离 vector STL 实现的影响,一个有趣的实验是使用 Clang 进行编译,并结合 VS 头文件。事实上,将 Clang 5.0.0 与 Visual Studio 2015 header 一起使用,会生成以下代码 - 显然,STL 实现具有巨大的影响!
    main:                                   # @main
    .Lfunc_begin0:
    .Lcfi0:
    .seh_proc main
    .seh_handler __CxxFrameHandler3, @unwind, @except
    # BB#0: # %.lr.ph
    pushq %rbp
    .Lcfi1:
    .seh_pushreg 5
    pushq %rsi
    .Lcfi2:
    .seh_pushreg 6
    pushq %rdi
    .Lcfi3:
    .seh_pushreg 7
    pushq %rbx
    .Lcfi4:
    .seh_pushreg 3
    subq $72, %rsp
    .Lcfi5:
    .seh_stackalloc 72
    leaq 64(%rsp), %rbp
    .Lcfi6:
    .seh_setframe 5, 64
    .Lcfi7:
    .seh_endprologue
    movq $-2, (%rbp)
    movl $16, %ecx
    callq "??2@YAPEAX_K@Z"
    movq %rax, -24(%rbp)
    leaq 16(%rax), %rcx
    movq %rcx, -8(%rbp)
    movups .L.ref.tmp(%rip), %xmm0
    movups %xmm0, (%rax)
    movq %rcx, -16(%rbp)
    movl 4(%rax), %ebx
    movl 8(%rax), %esi
    movl 12(%rax), %edi
    .Ltmp0:
    leaq -24(%rbp), %rcx
    callq "?_Tidy@?$vector@HV?$allocator@H@std@@@std@@IEAAXXZ"
    .Ltmp1:
    # BB#1: # %"\01??1?$vector@HV?$allocator@H@std@@@std@@QEAA@XZ.exit"
    addl %ebx, %esi
    leal 1(%rdi,%rsi), %eax
    addq $72, %rsp
    popq %rbx
    popq %rdi
    popq %rsi
    popq %rbp
    retq
    .seh_handlerdata
    .long ($cppxdata$main)@IMGREL
    .text
    更新 - Visual Studio 2017
    在 Visual Studio 2017 中, <vector>正如在此 blog post 上宣布的那样,已经进行了重大改革来自 Visual C++ 团队。具体来说,它提到了以下优化:
    • Eliminated unnecessary EH logic. For example, vector’s copy assignment operator had an unnecessary try-catch block. It just has to provide the basic guarantee, which we can achieve through proper action sequencing.

    • Improved performance by avoiding unnecessary rotate() calls. For example, emplace(where, val) was calling emplace_back() followed by rotate(). Now, vector calls rotate() in only one scenario (range insertion with input-only iterators, as previously described).

    • Improved performance with stateful allocators. For example, move construction with non-equal allocators now attempts to activate our memmove() optimization. (Previously, we used make_move_iterator(), which had the side effect of inhibiting the memmove() optimization.) Note that a further improvement is coming in VS 2017 Update 1, where move assignment will attempt to reuse the buffer in the non-POCMA non-equal case.


    好奇,我回去测试这个。在 Visual Studio 2017 中构建示例时,结果仍然是多页程序集列表,有很多函数调用,因此即使代码生成改进,也很难注意到。
    但是,在使用 clang 5.0.0 和 Visual Studio 2017 header 构建时,我们得到以下程序集:
    main:                                   # @main
    .Lcfi0:
    .seh_proc main
    # BB#0:
    subq $40, %rsp
    .Lcfi1:
    .seh_stackalloc 40
    .Lcfi2:
    .seh_endprologue
    movl $16, %ecx
    callq "??2@YAPEAX_K@Z" ; void * __ptr64 __cdecl operator new(unsigned __int64)
    movq %rax, %rcx
    callq "??3@YAXPEAX@Z" ; void __cdecl operator delete(void * __ptr64)
    movl $10, %eax
    addq $40, %rsp
    retq
    .seh_handlerdata
    .text
    请注意 movl $10, %eax指令 - 也就是说,使用 VS 2017 的 <vector> , clang 能够折叠所有内容,预先计算 10 的结果,并且只保留对 new 和 delete 的调用。
    我会说这是非常了不起的!
    函数内联
    在这个例子中,函数内联可能是最重要的优化。通过将被调用函数的代码折叠到它们的调用点,编译器能够对合并后的代码进行进一步的优化,此外,删除函数调用有利于减少调用开销和消除优化障碍。
    当检查 VS 生成的程序集,并比较内联前后的代码( Compiler Explorer )时,我们可以看到大多数 vector 函数确实被内联了,除了分配和释放函数。特别是,有拨打 memmove的电话。 ,这是一些更高级别函数的内联结果,例如 _Uninitialized_copy_al_unchecked . memmove是一个库函数,因此不能内联。然而,clang 有一个聪明的方法来解决这个问题——它取代了对 memmove 的调用。拨打 __builtin_memmove . __builtin_memmove是一个内置/内在函数,与 memmove 具有相同的功能,但与普通函数调用相反,编译器为其生成代码并将其嵌入到调用函数中。因此,可以在调用函数内部进一步优化代码,并最终将其作为死代码删除。
    摘要
    总而言之,在这个例子中,Clang 明显优于 VS,这要归功于高质量的优化和更高效的 vector STL 实现。当对 Visual C++ 和 clang(Visual Studio 2017 头文件)使用相同的头文件时,Clang 击败了 Visual C++。
    写这个回答的时候,我不禁在想,如果没有 Compiler Explorer我们该怎么办? ?感谢 Matt Godbolt 提供了这个神奇的工具!

    关于c++ - Visual C++ 优化选项 - 如何改进代码输出?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48585531/

    25 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com