使用类和公共成员优化输出值

时间:2010-05-21 15:37:39

标签: c++ optimization return-value

假设你有一个函数,并且每次函数返回一个大对象时你都会调用它。我使用返回void的仿函数优化了问题,并将返回值存储在公共成员中:

#include <vector>
const int N = 100;

std::vector<double> fun(const std::vector<double> & v, const int n)
{
    std::vector<double> output = v;
    output[n] *= output[n];
    return output;
}

class F
{
public:
    F() : output(N) {};
    std::vector<double> output;
    void operator()(const std::vector<double> & v, const int n)
 {
     output = v;
     output[n] *= n;
  }
};


int main()
{
    std::vector<double> start(N,10.);
    std::vector<double> end(N);
    double a;

    // first solution
    for (unsigned long int i = 0; i != 10000000; ++i)
      a = fun(start, 2)[3];

    // second solution
    F f;
    for (unsigned long int i = 0; i != 10000000; ++i)
    {
     f(start, 2);
     a = f.output[3];
 }
}

是的,我可以使用内联或以其他方式优化此问题,但在这里我想强调这个问题:使用函数我只使用函数声明和构造输出变量output一次我每次打电话都这样做。第二种解决方案比使用g++ -O1g++ -O2的第一种解决方案快两倍。你怎么看待它,这是一个丑陋的优化吗?

编辑:

澄清我的目标。我必须评估函数&gt; 10M次,但我只需要几次随机输出。输入没有改变很重要,实际上我将它声明为const引用。在这个例子中,输入总是相同的,但在现实世界中输入改变,它是函数的先前输出的函数。

5 个答案:

答案 0 :(得分:1)

更常见的情况是在函数外部创建保留足够大的对象,并通过指针或引用将大对象传递给函数。您可以在对函数的多次调用中重用此对象。因此,您可以减少持续的内存分配。

答案 1 :(得分:1)

在这两种情况下,您都要多次分配新的矢量。

您应该做的是将输入和输出对象传递给您的类/函数:

void fun(const std::vector<double> & in, const int n, std::vector<double> & out)
{
    out[n] *= in[n];
}

这样您就可以将逻辑与算法分开。你必须创建一个新的std :: vector并将它传递给函数多次。请注意,没有必要进行复制/分配。

P.S。自从我做c ++以来已经有一段时间了。它可能无法立即编译。

答案 2 :(得分:1)

这不是一个丑陋的优化。它实际上是一个相当不错的。

但是,我会隐藏输出并让operator []成员访问其成员。为什么?因为您可以通过将所有数学运算移动到该函数来执行延迟评估优化,因此仅在客户端请求该值时才执行该数学运算。在用户要求之前,为什么要这样做?

编辑:

刚检查了标准。赋值运算符的行为基于insert()。该函数的注释表明如果新大小超过当前容量,则会发生分配。当然,这似乎并没有明确地禁止实现重新分配,即使不是......我很确定你会发现没有这样做,我确信标准在其他地方说了些什么。因此,您通过删除分配调用来提高速度。

您仍应隐藏内部矢量。如果使用封装,您将有更多机会更改实现。您还可以从函数向向量返回引用(可能是const)并保留原始语法。

答案 3 :(得分:1)

我玩了一下,并提出了下面的代码。我一直在想有更好的方法来做到这一点,但现在它正在逃避我。

关键区别:

  • 我对公共成员变量过敏,因此我制作了output private,并在其周围放置了吸气剂。
  • 优化时不需要运算符返回void,所以我将它作为const引用返回,这样我们就可以保留返回值语义。
  • 我尝试将方法概括为模板化基类,因此您可以为特定的返回类型定义派生类,而不是重新定义管道。这假设您要创建的对象采用单参数构造函数,并且您要调用的函数接受另一个参数。我认为如果这种情况有所不同,你必须定义其他模板。

...享受

#include <vector>

template<typename T, typename ConstructArg, typename FuncArg>
class ReturnT
{
    public:
        ReturnT(ConstructArg arg): output(arg){}
        virtual ~ReturnT() {}
        const T& operator()(const T& in, FuncArg arg) 
        {   
            output = in;
            this->doOp(arg);
            return this->getOutput();
        }
        const T& getOutput() const {return output;}
    protected:
        T& getOutput() {return output;}
    private:
        virtual void doOp(FuncArg arg) = 0;

        T output;
};


class F : public ReturnT<std::vector<double>, std::size_t, const int>
{
    public:
        F(std::size_t size) : ReturnT<std::vector<double>, std::size_t, const int>(size) {}
    private:
        virtual void doOp(const int n)
        {
            this->getOutput()[n] *= n;
        }
};

int main()
{
    const int N = 100;
    std::vector<double> start(N,10.);
    double a;


    // second solution
    F f(N);
    for (unsigned long int i = 0; i != 10000000; ++i)
    {
        a = f(start, 2)[3];
    }
}

答案 4 :(得分:0)

看起来很奇怪(我的意思是需要进行优化) - 我认为在这种情况下,一个体面的编译器应该执行return value optimization。也许您只需要启用它。