18

对于一个函数/方法包含许多输入参数,如果以不同的顺序传入是否会有所不同?如果有,在哪些方面(可读性,效率,...)?我对自己的功能/方法应该如何做更好奇?

在我看来,这:

  1. 通过引用/指针传递的参数通常在通过值传递的参数之前。例如:

    void* memset( void* dest, int ch, std::size_t count ); 
    
  2. 目标参数通常位于源参数之前。例如:

    void* memcpy( void* dest, const void* src, std::size_t count );
    
  3. 除了一些硬约束,即具有默认值的参数必须排在最后。例如:

    size_type find( const basic_string& str, size_type pos = 0 ) const;
    
  4. 无论它们以什么顺序传递,它们都是功能等效的(实现相同的目标)。

4

4 回答 4

12

有几个原因可能很重要 - 下面列出了。C++ 标准本身并没有强制要求在这个领域有任何特定的行为,因此没有可移植的方法来推断性能影响,即使某个可执行文件中的某些东西明显(稍微)更快,程序中的任何地方或编译器都有变化选项或版本,可能会删除甚至逆转早期的好处。在实践中,很少听到人们谈论参数排序在他们的性能调整中具有任何意义。如果您真的很在意,最好检查您自己的编译器的输出和/或基准测试结果代码。

例外

传递给函数参数的表达式的求值顺序是未指定的,它很可能会受到源代码中出现顺序的更改的影响,某些组合在 CPU 执行管道中效果更好,或者更早引发异常这会使其他一些参数准备短路。如果某些参数是分配/构造和破坏/解除分配代价高昂的临时对象(例如表达式的结果),这可能是一个重要的性能因素。同样,对程序的任何更改都可能消除或逆转之前观察到的好处或惩罚,因此如果您关心这一点,您应该为要在进行函数调用之前首先评估的参数创建一个命名临时。

寄存器与缓存(堆栈内存)

一些参数可以在寄存器中传递,而其他参数则被推入堆栈 - 这实际上意味着至少进入最快的 CPU 缓存,并意味着它们的处理可能更慢。

如果函数最终还是访问了所有参数,并且选择是将参数 X 放在寄存器中,将 Y 放在堆栈中,反之亦然,那么它们如何传递并不重要,但考虑到函数可能有条件影响实际使用哪些变量(如果可能输入或不输入的语句、开关、循环、提前返回或中断等),如果实际不需要的变量在堆栈中而需要的变量在堆栈中,则可能会更快一个寄存器。

有关调用约定的一些背景和信息,请参见http://en.wikipedia.org/wiki/X86_calling_conventions

对齐和填充

从理论上讲,性能可能会受到参数传递约定的细节影响:参数可能需要对堆栈上的任何 - 或者可能只是全速 - 访问进行特殊对齐,并且编译器可能会选择填充而不是重新排序它推送的值 - 它是除非参数数据在缓存页面大小的范围内,否则很难想象这很重要

非绩效因素

您提到的其他一些因素可能非常重要-例如,我倾向于将任何非常量指针和引用放在第一位,并将函数命名为 load_xxx,因此我对可以修改哪些参数以及修改顺序有一致的期望通过他们。不过,没有特别占主导地位的约定。

于 2014-01-02T10:57:54.403 回答
3

严格来说,这并不重要——参数被压入堆栈,函数通过某种方式从堆栈中获取它们来访问它们。

但是,大多数 C/C++ 编译器允许您指定替代调用约定。例如,Visual C++ 支持__fastcall约定,它将前 2 个参数存储在 ECX 和 EDX 寄存器中,这(理论上)应该可以在适当的情况下提高性能。

还有__thiscallthis指针存储在 ECX 寄存器中。如果您正在使用 C++,那么这可能很有用。

于 2014-01-02T10:48:03.973 回答
3

这里有一些答案提到了调用约定。它们与您的问题无关:无论您使用什么调用约定,您声明参数的顺序都无关紧要。寄存器传递哪些参数,堆栈传递哪些参数并不重要,只要寄存器传递的参数数量相同,堆栈传递的参数数量相同即可。请注意,大小高于原生架构大小(32 位为 4 字节,64 位为 8 字节)的参数是通过地址传递的,因此它们以与较小尺寸数据相同的速度传递.

举个例子:

您有一个带有 6 个参数的函数。你有一个调用约定,我们称之为 CA,它通过寄存器传递一个参数,其余的(在这种情况下为 5 个)通过堆栈传递,第二个调用约定,让我们称之为 CB,它通过寄存器传递 4 个参数,其余的(在本例中为 2)按堆栈。

现在,当然 CA 会比 CB 快,但它与声明参数的顺序无关。对于 CA,无论您首先声明哪个参数(通过寄存器)以及声明第二个、第三个..6 个(堆栈),它都会一样快,而对于 CB,无论您为寄存器声明哪个 4 个参数,它都会一样快并且您将其声明为最后两个参数。


现在,关于你的问题:

唯一的强制性规则是可选参数必须最后声明。任何非可选参数都不能跟在可选参数之后。

除此之外,你可以使用任何你想要的顺序,我能给你的唯一强烈建议是保持一致。选择一个模型并坚持下去。

您可以考虑的一些准则:

  • 目的地在源之前。这是要接近的destination = source
  • 缓冲区的大小在缓冲区之后:f(char * s, unsigned size)
  • 首先输入参数,最后输出参数(这和我给你的第一个冲突)

但是对于参数的顺序,没有“错误”或“正确”,甚至没有普遍接受的准则。选择一些东西并保持一致。

编辑

我想到了一种对参数进行排序的“错误”方式:按字母顺序:)。

编辑 2

例如,对于 CA,如果我传递一个 vector(100) 和一个 int,最好先使用 vector(100),即使用寄存器加载更大的数据类型。对?

不,正如我所提到的,数据大小并不重要。让我们谈谈 32 位架构(同样的讨论适用于任何架构 16 位、64 位等)。让我们分析一下参数大小与架构的原生大小相关的 3 种情况。

  • 相同大小:4 字节参数。这里没什么好说的。
  • 较小的尺寸:将使用 4 字节的寄存器或将在堆栈上分配 4 字节。所以这里也没什么有趣的。
  • 更大的尺寸:(例如具有许多字段的结构或静态数组)。无论选择哪种方法来传递此参数,此数据都驻留在内存中,并且传递的是指向该数据的指针(大小为 4 字节)。同样,我们在堆栈上有一个 4 字节的寄存器或 4 字节。

参数的大小无关紧要。

编辑 3

@TonyD 如何解释,如果您不访问所有参数,顺序很重要。看他的回答。

于 2014-01-02T11:33:15.683 回答
1

我不知何故找到了一些相关的页面。

https://softwareengineering.stackexchange.com/questions/101346/what-is-best-practice-on-ordering-parameters-in-a-function

https://google.github.io/styleguide/cppguide.html#Function_Parameter_Ordering

所以首先谷歌的 C++ 风格并没有真正回答这个问题,因为它没有回答输入参数或输出参数中的实际顺序。

其他页面基本上建议订单参数在某种意义上易于理解和使用。

为了可读性,我个人更喜欢根据字母顺序来排序参数。但是您也可以采用一些策略来命名参数以使其井井有条,以便它们仍然易于理解和使用。

于 2016-07-12T17:26:29.623 回答