Performance 为什么不是';t通过引用传递结构是一种常见的优化吗?

Performance 为什么不是';t通过引用传递结构是一种常见的优化吗?,performance,optimization,compiler-construction,assembly,struct,Performance,Optimization,Compiler Construction,Assembly,Struct,直到今天,我一直认为,如果结构足够大,后者会更快,那么优秀的编译器会自动将结构的pass-by-value转换为pass-by-reference。就我所知,这似乎是一个不需要动脑筋的优化。然而,为了满足我对这是否发生的好奇,我在C++中创建了一个简单的测试用例,并查看了GCC和数字MARS的输出。当所有的函数都是将成员加起来并返回值时,它们都坚持按值传递32字节结构。没有传入对结构的修改。C++版本如下: #include "iostream.h" struct S { int i,

直到今天,我一直认为,如果结构足够大,后者会更快,那么优秀的编译器会自动将结构的pass-by-value转换为pass-by-reference。就我所知,这似乎是一个不需要动脑筋的优化。然而,为了满足我对这是否发生的好奇,我在C++中创建了一个简单的测试用例,并查看了GCC和数字MARS的输出。当所有的函数都是将成员加起来并返回值时,它们都坚持按值传递32字节结构。没有传入对结构的修改。C++版本如下:

#include "iostream.h"

struct S {
    int i, j, k, l, m, n, o, p;
};

int foo(S s) {
    return s.i + s.j + s.k + s.l + s.m + s.n + s.o + s.p;
}

int main() {
    S s;
    int bar = foo(s);
    cout << bar;
}
#包括“iostream.h”
结构{
inti,j,k,l,m,n,o,p;
};
国际足联(S){
返回s.i+s.j+s.k+s.l+s.m+s.n+s.o+s.p;
}
int main(){
S S;
int bar=foo(s);

cout一个答案是编译器需要检测被调用的方法是否以任何方式修改了结构的内容。如果它这样做了,那么通过引用传递的效果将不同于通过值传递的效果。

问题是您要求编译器对用户代码的意图做出决定。也许我想要我的超大型结构将按值传递,以便我可以在复制构造函数中执行某些操作。相信我,在这种情况下,有人需要在复制构造函数中有效地调用某些内容。切换到by ref将绕过复制构造函数


将此作为编译器生成的决策是一个坏主意。原因是,这使得无法对代码流进行推理。您无法查看调用并知道调用的具体功能。您必须a)了解代码,b)猜测编译器优化。

某些语言的编译器确实无法做到这一点如果他们能够访问被调用的函数,并且如果他们可以假设调用函数不会改变。有时这被称为全局优化,并且看起来某些C或C++编译器实际上会优化这样的情况,更可能通过将代码嵌入到这样一个平凡函数中。任何按值传递的理由,以及让编译器优化您的意图可能会破坏您的代码

例如,如果被调用函数以任何方式修改了结构。如果您希望将结果传递回调用方,那么您可以传递指针/引用,也可以自己返回

您要求编译器做的是更改代码的行为,这将被视为编译器错误


如果您想进行优化并通过引用传递,那么请务必修改某人现有的函数/方法定义以接受引用;这并不是很难做到的。您可能会对自己在未意识到的情况下造成的破坏感到惊讶。

不要忘记,在C/C++中,编译器需要能够编译对函数的调用仅基于函数声明的初始化

鉴于调用者可能只使用该信息,编译器无法编译函数以利用您所说的优化。调用者不知道函数不会修改任何内容,因此它不能按引用传递。由于某些调用者可能由于缺少详细信息而按值传递函数必须假设按值传递进行编译,每个人都需要按值传递

请注意,即使您将参数标记为“
const
”,编译器仍然无法执行优化,因为函数可能存在漏洞并丢弃了constness(这是允许的,并且定义良好,只要传入的对象实际上不是const)

我认为对于静态函数(或匿名命名空间中的函数),编译器可能会进行您所说的优化,因为函数没有外部链接。只要函数的地址没有传递给其他例程或存储在指针中,就不应该从其他代码调用它。在这种情况下,编译器可能完全了解所有调用方,所以我想t可以进行优化

我不确定是否有(事实上,如果有,我会感到惊讶,因为它可能不会经常应用)


当然,作为程序员(使用C++时)您可以尽可能使用
const&
参数强制编译器执行此优化。我知道您在问为什么编译器不能自动执行此优化,但我认为这是次好的选择。

从“按值”更改为“按引用”将更改函数的签名。如果函数不是静态的,则此可能会导致其他编译单元的链接错误,而这些编译单元不知道您所做的优化。

事实上,进行此类优化的唯一方法是通过某种链接后全局优化阶段。众所周知,这很难做到,但有些编译器在某种程度上做到了这一点。

好吧,简单的答案是结构在内存中的位置不同,因此传递的数据也不同。我认为答案越复杂,正在穿线

编译器需要检测a)foo没有修改结构;b)foo没有对结构元素的物理位置进行任何计算;c)调用方或调用方生成的另一个线程在foo完成运行之前没有修改结构


在您的示例中,可以想象编译器可以做这些事情,但是节省的内存是无关紧要的,可能不值得猜测。如果您使用具有200万个元素的结构运行同一个程序,会发生什么情况?

我认为这绝对是您可以实现的优化(在一些假设下,见最后一段),但我不清楚它是否有利可图。与其把争论推到一边
double x; // using non structs, oh-well

void Foo(double d)
{
      x += d; // ok
      x += d; // Oops
}

void main()
{
     x = 1;
     Foo(x);
}
struct FOO { ... };

void func1(struct FOO *foo1);
void func2(struct FOO foo2);

void test(void)
{
  struct FOO foo;
  func1(&foo);
  func2(foo);
}
__attribute__((noinline))
int foo(S s) {
    return s.i + s.j + s.k + s.l + s.m + s.n + s.o + s.p;
}

int bar(S s) {
    return foo(s);
}
foo(S):
        mov     eax, DWORD PTR [rsp+12]
        add     eax, DWORD PTR [rsp+8]
        add     eax, DWORD PTR [rsp+16]
        add     eax, DWORD PTR [rsp+20]
        add     eax, DWORD PTR [rsp+24]
        add     eax, DWORD PTR [rsp+28]
        add     eax, DWORD PTR [rsp+32]
        add     eax, DWORD PTR [rsp+36]
        ret
bar(S):
        jmp     foo(S)
int bar(S s) {
    return foo(s) + 1;
}
bar(S):
        push    QWORD PTR [rsp+32]
        push    QWORD PTR [rsp+32]
        push    QWORD PTR [rsp+32]
        push    QWORD PTR [rsp+32]
        call    foo(S)
        add     rsp, 32
        add     eax, 1
        ret
int bar(S s) {
    s.i += 1;
    return foo(s);
}
bar(S):
        add     DWORD PTR [rsp+8], 1
        jmp     foo(S)