当前位置: 首页 > 知识库问答 >
问题:

没有执行循环分裂/不变优化,为什么?

卓星波
2023-03-14

我正在尝试了解更多关于程序集的信息,以及编译器可以做什么和不能做什么优化。

我有一段测试代码,对此我有一些问题。

在此处查看其实际操作:https://godbolt.org/z/pRztTT,或检查下面的代码和程序集。

#include <stdio.h>
#include <string.h>

int main(int argc, char* argv[])
{
        for (int j = 0; j < 100; j++) {
                if (argc == 2 && argv[1][0] == '5') {
                        printf("yes\n");
                }
                else {
                        printf("no\n");
                }
        }

        return 0;
}

GCC 10.1 生产的 -O3 组件:

.LC0:
        .string "no"
.LC1:
        .string "yes"
main:
        push    rbp
        mov     rbp, rsi
        push    rbx
        mov     ebx, 100
        sub     rsp, 8
        cmp     edi, 2
        je      .L2
        jmp     .L3
.L5:
        mov     edi, OFFSET FLAT:.LC0
        call    puts
        sub     ebx, 1
        je      .L4
.L2:
        mov     rax, QWORD PTR [rbp+8]
        cmp     BYTE PTR [rax], 53
        jne     .L5
        mov     edi, OFFSET FLAT:.LC1
        call    puts
        sub     ebx, 1
        jne     .L2
.L4:
        add     rsp, 8
        xor     eax, eax
        pop     rbx
        pop     rbp
        ret
.L3:
        mov     edi, OFFSET FLAT:.LC0
        call    puts
        sub     ebx, 1
        je      .L4
        mov     edi, OFFSET FLAT:.LC0
        call    puts
        sub     ebx, 1
        jne     .L3
        jmp     .L4

GCC似乎产生了两个版本的循环:一个具有argv[1][0]=='5'条件但没有argc==2条件,另一个没有任何条件。

我的问题:

  • 是什么阻止了海湾合作委员会分裂出全部条件?它与这个问题类似,但代码没有机会在这里获得指向argv的指针。
  • 在没有任何条件的循环中(L3在组装中),为什么循环体是重复的?这是为了减少跳跃次数,同时仍然适合某种缓存吗?

共有1个答案

冯良才
2023-03-14

GCC不知道< code>printf不会修改< code>argv指向的内存,所以它不能将检查抛出循环。

argc是一个局部变量(不能被任何指针全局变量指向),因此它知道调用不透明函数不能修改它。证明局部变量是真正私有的是Escape Analysis的一部分。

OP通过首先将argv[1][0]复制到本地char变量中来测试这一点:这让GCC将完整条件从循环中提升出来。

在实践中,argv[1]不会指向打印f可以修改的内存。但是我们只知道,因为printf是一个C标准库函数,我们假设main只是由CRT启动代码用实际的命令行args调用的。不是通过此程序中传递其自身参数的其他函数。在C中(与C不同),main是重入的,可以从程序中调用。

此外,在GNU C中,printf可以在其中注册自定义格式字符串处理函数。尽管在这种情况下,编译器内置的printf查看格式字符串并将其优化为put调用。

因此,printf已经部分特殊,但我不认为GCC会费心去寻找基于它的优化,而不是修改任何其他全局可访问的内存。使用自定义 stdio 输出缓冲区,这甚至可能不是真的。打印速度很慢;节省一些溢出/重新加载它周围通常没什么大不了的。

(理论上)将puts()和main()一起编译会让编译器看到puts()没有触及argv并完全优化循环吗?

是的,例如,如果您编写了自己的write函数,该函数在系统调用instruction周围使用内联asm语句(使用仅内存输入的操作数以确保安全,同时避免 撞击),那么它可以内联,并假设 argv[1]。

或者在没有内联的情况下进行过程间优化。

Re: 展开:这很奇怪,默认情况下,GCC 在 -O3 处不启用 -funroll-循环,而只使用 -O3 -f轮廓使用。或者,如果手动启用。

 类似资料:
  • 同样的for循环语句,在spring环境中执行和不在spring环境中执行,耗时不同。 代码的for循环是一个常用的用key加密文件的方法 在spring中执行耗时 controller耗时:399 controller耗时:368 controller耗时:367 controller耗时:366 controller耗时:368 controller耗时:367 controller耗时:36

  • 今天我开始玩分支,检查两个布尔值。我很确定,在某些优化级别上,它们将简单地添加并检查,但gcc和CLANG不是这样。为什么gcc不优化两个bool检查,用addition和一个check替换它们?让我给你看一个例子: 两个分支(test+je)不应该比加法和分支(add+jne)慢吗? 编辑:我真正的意思是乘法,因为在true和false的情况下(1+0),加法给出true(1),但乘法给出正确的

  • 在玩代码时,我注意到一个奇怪的行为,我不知道如何解释背后的逻辑 我希望执行次。然后我看到它继续运行(想象是100000而不是10),并且假设开发人员(VS)将(这是意料之中的),因此每次进入时输出都是次。 但后来证明从未初始化。 所以我的问题是为什么?这是一种未定义的行为吗?这不是一个标准的代码吗?

  • } 链接:https://www.hackerrank.com/challenges/java-string-compare/problem

  • 我正在构建一个gradebook来存储学生和教师,每个学生和教师都有一个唯一的ID,以及他们各自在Student和Teacher对象的ArrayList中注册或教学的类。我有文件夹路径“j:/compsci/类/”,为每个类存储一个文本文件。 文本文件格式: 第1行:班级名称、教师ID、期间、荣誉?、班级ID 第2行:班级中每个学生的学生ID(用逗号分隔)。 在这里,我初始化了每个学生正在接受的所

  • 我对python中双for循环的使用感到困惑,这是我的代码: 输出如下: 它只对外循环的第一个值执行内循环,为什么会发生这种情况?我怎样才能让它在第一个和第二个变量的所有组合上循环?