链式调用函数会产生额外开销吗?

5

在调用函数链中是否存在开销?例如,在以下for循环中:

for(int i = 0; i < n; i++)
{
  var=object.method1().method2().method3();
}

第一种方法比第二种方法效率低吗?
var = object.method1().method2();
for(int i = 0; i < n; i++)
{
  var.method3();
}

我的关注点是了解函数调用/返回是否会有开销,而不是函数内部的操作。

谢谢。


3
当然不会,他使用占位符词语。不过这些例子完全清楚明白。 - Zong
当您在对象中调用方法时,通常会发生的情况是将该方法传递给'this'指针作为隐藏参数,这通常意味着将其推送到堆栈或保存到寄存器中。此值仅在链接时使用多次,因此不会产生任何开销(至少在优化之后不会)。请注意,如果未启用优化,则编译器实际上可能会保存每个调用返回的“this”。 - Skeen
需要进行静态分析以确保您确实返回了'this',才能使优化“生效”。 - Skeen
5个回答

3

你的两个片段在功能上并不相同。

你的原始问题标记为c++/Java,所以我们就用这个。一个函数式语言可能会有不同的处理方式。

通常情况下,第二个版本更快,因为method1().method2()只需要调用一次。在C++和Java中,编译器很难确定调用method1().method2()是否具有任何副作用。例如考虑一个接受用户输入的方法。

如果方法具有副作用,则编译器无法对var = object.m1().m2().m3()中调用的数量进行任何优化。

链式调用本身并不产生任何开销。


2

考虑到这是伪代码,第二个应该更快,因为你不需要在每次迭代中调用method1method2


1
虽然是真的,但我怀疑这不是问题的本意 - John Dibling
@JohnDibling 是的,看起来OP可能想要一个关于方法调用工作原理及其性能特征的低级解释。 - Zong

1

如果你在提到方法级联,你会得到这个:

class Object
{
public:
    Object& Method1()
    {
        // do something
        return *this;
    }

    Object& Method2()
    {
        // do something
        return *this;
    }

    Object& Method3()
    {
        // do something
        return *this;
    }    
};

因此,像这样调用函数

obj.Method1().Method2().Method3();

功能上等同于

obj.Method1();
obj.Method2();
obj.Method3();

简而言之,没有性能损失。这只是一种方便的习惯用法。你面临的唯一问题是,由于你被迫返回对this的引用,所以你不能返回有意义的东西(例如这些函数的结果)。
如果你指的是方法链
每个方法必须返回一些具有相应链中后续方法的对象:
class Object
{
public:
    std::vector<int>& Method1()
    {
        // do something
        return _vec;
    }
private:
    std::vector<int> _vec;
};

所以调用

obj.Method1.size();

等同于调用

std::vector<int>& vec = obj.Method1();
vec.size();

所以,再次强调,没有性能损失。

然而,如果你将Method1改为通过复制而不是引用返回,可能会有性能损失(例如,如果向量实际上被复制)。

虽然我认为你的问题表述不太清楚。你的第二个例子每次迭代少调用2个函数,因此比每次调用3个函数更有效率。但是,我不认为这真的是你想问的。


当然,我正要这么做。这个解释假设了太多。我们真的不能保证这些方法中的任何一个返回什么。我们不能假设它们只返回*this,因为我们不知道对象的API或其他任何信息。也许它们是三个不同的对象?也许它们在物理内存中相距很远,访问它们会引起缓存未命中?也许每次调用Method1().Method2()时都存在副作用? - scriptocalypse
1
@scriptocalypse 不完全是这样。方法必须返回某些内容,每个内容都必须有相应的方法定义用于链中的下一个项目。根据问题的结构,我认为他指的是方法级联(如果不是,我可以相应地编辑答案),但这个答案并不是不正确的,这通常是负投票所代表的。 - Zac Howland
啊,如果我误解了问题并且问题明确指的是返回*this,那么它仍然取决于语言和平台。假设是c++,并且假设函数没有副作用,那么你是正确的。但其他平台和语言不一定具有相同的性能。例如:当我还是Flash开发人员时,我从不会像那样链接,因为Flash播放器中的函数调用开销非常大。即使这个问题被标记为与语言无关,它可能实际上不能被这样回答。 - scriptocalypse
@scriptocalypse 当我回答这个问题时,它被标记为C++和Java。这两种语言都允许这种类型的行为而不会影响性能。这并不是说你不能强制出现性能问题...但实际的链接并不会导致这种情况。 - Zac Howland
好的,那当然可以解释事情。我会投票支持重新启用旧标签,因为这是一个重要的区分。 - scriptocalypse

0

这真的取决于你的编译器有多聪明,但是假设编译器没有进行特殊优化,在你列出的例子中,你肯定最好使用第二个示例。在循环之前预缓存 `object.method1().method2()` 的返回值绝对会节省时间,因为你不必每次迭代时调用那些函数或访问那些变量。

调用单个方法的开销比简单访问本地变量要大,在你的例子中,你调用了两个方法来访问单个返回值。


1
方法1和方法2都可能具有副作用。 - Captain Giraffe
绝对正确 @Captain Giraffe。不过,这取决于编程语言、平台和编译器。例如,在Unity中使用C#属性时,如果属性没有副作用(真正只是一个get/set),与类级别字段访问相比,似乎没有任何开销。而在Flash中,无论有什么副作用,使用属性都会带来普通函数调用的所有开销。 - scriptocalypse
在这个通用的上下文中,我认为我们需要考虑在任何方法中使用thread.sleep(int_max)的可能性。 - Captain Giraffe

0
在第一个例子中,method1()和method2()被调用了“n”次。而在第二个例子中,method1()和method2()只被调用了一次。如果你所说的“高效”是指“花费最少的时间”,那么第二个例子肯定更加高效。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接