C 为什么没有编译器能够优化此代码?

C 为什么没有编译器能够优化此代码?,c,optimization,C,Optimization,考虑以下C代码,假设80位长的双音符,我确实知道memcmp,这只是一个实验: enum { sizeOfFloat80=10 }; // NOTE: sizeof(long double) != sizeOfFloat80 _Bool sameBits1(long double x, long double y) { for(int i=0;i<sizeOfFloat80;++i) if(((char*)&x)[i]!=((char*)&y)[i]

考虑以下C代码,假设80位长的双音符,我确实知道memcmp,这只是一个实验:

enum { sizeOfFloat80=10 }; // NOTE: sizeof(long double) != sizeOfFloat80
_Bool sameBits1(long double x, long double y)
{
    for(int i=0;i<sizeOfFloat80;++i)
        if(((char*)&x)[i]!=((char*)&y)[i])
            return 0;
    return 1;
}
这看起来很难看,每个字节都有分支,事实上似乎根本没有经过优化,但至少循环被展开了。很容易看出,这可以优化为与以下代码等效的代码,并且通常用于更大的数据以使用更大的步幅:

#include <string.h>
_Bool sameBits2(long double x, long double y)
{
    long long X=0; memcpy(&X,&x,sizeof x);
    long long Y=0; memcpy(&Y,&y,sizeof y);
    short Xhi=0; memcpy(&Xhi,sizeof x+(char*)&x,sizeof Xhi);
    short Yhi=0; memcpy(&Yhi,sizeof y+(char*)&y,sizeof Yhi);
    return X==Y && Xhi==Yhi;
}

所以我的问题是:为什么这三个编译器都不能进行这种优化?这在C代码中是很少见的吗?

首先,它无法进行此优化,因为您使用过多的内存重新解释来重载代码,从而完全混淆了代码的含义。这样的代码正好让编译器做出反应,我不知道这到底是什么,但如果这是你想要的,那就是你得到的。为什么您期望编译器甚至费心将一种内存重新解释转换为另一种内存重新解释呢!我完全不清楚

第二,从理论上讲,它可以做到这一点,但它在优先事项清单上的地位可能不是很高。请记住,代码优化通常是通过模式匹配算法完成的,而不是通过某种a.I.完成的,而这并不是它识别的模式之一

大多数情况下,手动尝试对代码执行低级优化会挫败编译器同样的努力。如果你想自己优化它,那就一直走下去。不要期望能够启动它,然后将它交给编译器来完成这项工作


比较两个长双值x和y非常容易:x==y。如果您想要进行位对位内存比较,那么在一个编译器中使用memcmp可能会使编译器的工作更容易,因为它本身就知道memcmp是内置的、固有的函数。

首先,它无法进行此优化,因为您使用过多的内存重新解释来重载代码,从而完全混淆了代码的含义。这样的代码正好让编译器做出反应,我不知道这到底是什么,但如果这是你想要的,那就是你得到的。为什么您期望编译器甚至费心将一种内存重新解释转换为另一种内存重新解释呢!我完全不清楚

第二,从理论上讲,它可以做到这一点,但它在优先事项清单上的地位可能不是很高。请记住,代码优化通常是通过模式匹配算法完成的,而不是通过某种a.I.完成的,而这并不是它识别的模式之一

大多数情况下,手动尝试对代码执行低级优化会挫败编译器同样的努力。如果你想自己优化它,那就一直走下去。不要期望能够启动它,然后将它交给编译器来完成这项工作


比较两个长双值x和y非常容易:x==y。如果您想要进行位对位内存比较,那么在一个编译器中使用memcmp可能会使编译器的工作更容易,因为它本身就知道memcmp是什么内置的、内在的函数。

如果您想要进行浮点比较,比较可以很容易地完成。如果你想一点一点比较的话就不那么容易了。我可以理解你的观点,但是OP的问题不是关于代码的一致性。而是基于什么原因使编译器不优化代码,这是完全合法的,即使看起来很难看。此外,Ruslan关于位比较的观点可能有一定的道理,浮点值的简单比较总是一个问题。我认为答案很简单,for循环的展开是最快的代码。无论如何,PellesC不会展开循环并生成与请求的代码非常接近的代码。很好地说明了memcmp的要点。即使问题的核心仍然是奇怪的优化。关于memcmp,我在OP中提到,我确实知道它,并且为了实验而故意选择不使用它。此外,我测试的三个编译器似乎都不能内联memcmp,尽管icc在那里调用了_intel_fast_memcmp。如果需要浮点比较,比较可以很容易地完成。如果你想一点一点比较的话就不那么容易了。我可以理解你的观点,但是OP的问题不是关于代码的一致性。而是基于什么原因使编译器不优化代码,这是完全合法的,即使看起来很难看。此外,Ruslan关于位比较的观点可能有一定的道理,浮点值的简单比较总是一个问题。我认为答案很简单,for循环的展开是最快的代码。无论如何,PellesC不会展开循环并生成与请求的代码非常接近的代码。
关于memcmp的观点很好。即使问题的核心仍然是奇怪的优化。关于memcmp,我在OP中提到,我确实知道它,并且为了实验而故意选择不使用它。此外,我测试的三个编译器似乎都不能内联memcmp,尽管icc在那里调用了_intel_fast_memcmp。可能存在未定义的行为。长双精度不需要有10个字节。你想用这些代码实现什么?它看起来很模糊,像是在寻找问题的解决方案。@Olaf正如我已经说过的,我假设这个大小是由于选择了目标Linux x86。我投赞成票。正如我在下面解释的,问题是关于优化,而不是代码的一致性。编译器似乎真的无法优化它。或者简单地说,输出确实是最好的优化,即使看起来很难看。这是糟糕的风格。不要在没有实际需要的情况下编写这样的代码。使用语言功能,编写完全符合标准的可移植代码。@Frankie_C:这样太宽泛了。并非所有关于为什么一些模糊的结构没有得到优化的问题都是有用的,也不是关于主题的。存在未定义行为的可能性。长双精度不需要有10个字节。你想用这些代码实现什么?它看起来很模糊,像是在寻找问题的解决方案。@Olaf正如我已经说过的,我假设这个大小是由于选择了目标Linux x86。我投赞成票。正如我在下面解释的,问题是关于优化,而不是代码的一致性。编译器似乎真的无法优化它。或者简单地说,输出确实是最好的优化,即使看起来很难看。这是糟糕的风格。不要在没有实际需要的情况下编写这样的代码。使用语言功能,编写完全符合标准的可移植代码。@Frankie_C:这样太宽泛了。并非所有关于为什么一些模糊结构没有得到优化的问题都是有用的,也不是关于这个主题的。
#include <string.h>
_Bool sameBits2(long double x, long double y)
{
    long long X=0; memcpy(&X,&x,sizeof x);
    long long Y=0; memcpy(&Y,&y,sizeof y);
    short Xhi=0; memcpy(&Xhi,sizeof x+(char*)&x,sizeof Xhi);
    short Yhi=0; memcpy(&Yhi,sizeof y+(char*)&y,sizeof Yhi);
    return X==Y && Xhi==Yhi;
}
sameBits2:
        sub     esp, 20
        mov     edx, DWORD PTR [esp+36]
        mov     eax, DWORD PTR [esp+40]
        xor     edx, DWORD PTR [esp+24]
        xor     eax, DWORD PTR [esp+28]
        or      edx, eax
        movzx   eax, WORD PTR [esp+48]
        sete    dl
        cmp     WORD PTR [esp+36], ax
        sete    al
        add     esp, 20
        and     eax, edx
        ret