在 C++ 中,将结构作为参数的自由函数是否比具有成员函数的类更快地执行相同的操作?

In C++, is a free function taking a struct as an argument faster than a class with a member function to do the same thing?

提问人:dscerutti 提问时间:8/13/2023 更新时间:8/13/2023 访问量:99

问:

我在这里指的是克劳斯·伊格尔伯格(Klaus Iglberger)的永恒演讲。我有一个(相对)简单的函数,它将单个浮点数作为输入,并将结果作为另一个浮点数返回。然而,该函数需要许多参数,这些参数必须预先计算(否则它根本不快)。在我看来,我可以创建一个结构来保存预计算,然后将其与函数的参数一起提供给一个自由函数以近似,或者编写一个类来将预计算保存为私有成员,并使用公共成员函数根据感兴趣的参数执行近似计算。

我不想在这里深入研究意见。可以说,这两种方法都与另一种方法一样干净,并导致代码与另一种方法一样清晰。 给成员函数一个有意义的名称,让开发人员知道近似值是怎么回事,然后用结构体调用自由函数或调用类公共函数,区别仅在于术语出现的顺序,一个有句点,另一个有逗号。fastApprox

struct StrPrecomp {

  StrPrecomp(float alpha_in);

  float c0;
  float c1;
  float c2;
};

float fastApprox(const StrPrecomp s, float x) {
  return s.c0 + ((s.c1 + (s.c2 * x)) * x);
}

class ClsPrecomp {
public:
  ClsPrecomp(float alpha_in);

  float fastApprox(float x) {
    return c0 + ((c1 + (c2 * x)) * x);
  };

private:
  float c0;
  float c1;
  float c2;
};

int main() {
  StrPrecomp ms(0.5);
  ClsPrecomp mc(0.5);
  printf("Struct Result: %12.4f\n", fastApprox(ms, 0.7071));
  printf("Class  Result: %12.4f\n", mc.fastApprox(0.7071));
}

在性能方面哪个更好?Klaus 站在自由函数的一边,因为这些类成员函数中隐含了指针。但在这种情况下真的有区别吗?下面我通过 const 值将结构提交给 free 函数,这对许多 C++ 开发人员来说是不合适的,但在这种情况下速度是相同的,因为下面没有或其他可复制的成员,这会导致大量偷偷摸摸的深度复制。如果我通过 const 引用提交结构,它本身就是一个指针,我希望在性能上与每个成员取消引用 this->c0 等的类没有什么不同。thisstd::vector

干杯,所有人。

C++ 指针 优化 结构体

评论

2赞 273K 8/13/2023
你传递一个结构副本,它不可能更快。
1赞 Jérôme Richard 8/13/2023
我希望这些函数在启用优化的情况下内联。如果是这样,编译器可以轻松删除副本,因此开销为 null。在这种情况下,我强烈建议您比较汇编代码。
1赞 Jérôme Richard 8/13/2023
顺便说一句,请注意,一个问题可以出于许多不同的原因被否决,“展示研究努力”就是其中之一(尽管我发现在没有任何评论的情况下对新用户的问题投反对票非常糟糕)。
0赞 Quimby 8/13/2023
没有什么能阻止您查看生成的程序集。
0赞 Jesper Juhl 8/13/2023
您是否检查(并测试过)编译器在启用优化时为每个版本生成的内容?

答:

1赞 Enlico 8/13/2023 #1

在这两种情况下,编译器应该能够基本生成相同的代码。

但请注意,您还没有编写两个等效的代码。例如,由于您尚未声明为 free 函数的相应签名,因此ClsPrecomp::fastApproxconstfastApprox

float fastApprox(StrPrecomp& s, float x);

此外,为了比较这两种解决方案的不同之处,您需要编译使用一种或另一种解决方案的翻译单元。但是请注意,您不能只是在 cpp 文件中编写一个类,编译它并从中获取任何内容。如果你这样做了,你最终将一无所获,基本上,因为你没有可以链接到其他目标文件的函数。一种可能性是两个编写两个翻译单元,它们都公开一个函数(在我的示例中)接受您的类的对象,并查看它们被编译为什么。work

像这样的东西:

  • 自由函数法
    // in header
    struct StrPrecomp {
    
      StrPrecomp(float alpha_in);
      float c0;
      float c1;
      float c2;
    };
    
    float fastApprox(StrPrecomp& s, float x);
    
    void work(StrPrecomp& obj);
    
    // in cpp
    StrPrecomp::StrPrecomp(float alpha_in) {}
    
    float fastApprox(StrPrecomp& s, float x) {
        return x;
    }
    
    void work(StrPrecomp& obj) {
        fastApprox(obj, 3);
    }
    
  • 成员函数方法
    // in header
    class ClsPrecomp {
    public:
      ClsPrecomp(float alpha_in);
    
      float fastApprox(float x);
    
    private:
      float c0;
      float c1;
      float c2;
    };
    
    void work(ClsPrecomp& obj);
    
    // in cpp
    ClsPrecomp::ClsPrecomp(float alpha_in) {}
    
    float ClsPrecomp::fastApprox(float x) {
        return x;
    }
    
    void work(ClsPrecomp& obj) {
        obj.fastApprox(3);
    }
    

如果单击这两个链接,您将看到为这两个转换单元生成的程序集基本相同。

2赞 Jarod42 8/13/2023 #2

与往常一样,对于性能,您必须进行衡量。

quick-bench.com 来看,对于您的示例,自由函数调用和方法调用给出了类似的结果。

顺便说一句,直觉上,我会说该方法主要是一个带有第一个参数的函数,因此类似于自由函数调用。this