在C语言中高效地返回多个值

4
当一个C函数需要返回多个值时,有几种方法可以实现。
目前我对其中两种方法的相对效率感兴趣:
a) 将这些值捆绑在一个结构体foo中。填充一个本地的foo,并返回它。
b) 传递指针以便填充。
(我正在处理一些混合了这两种方法的遗留代码。)
对于本文的目的:
  • 所有返回的值都是基本类型。整数、指针值等。因此sizeof(foo)非常小。
  • 使结构体foo不透明并不是一个问题。
  • 所讨论的函数最多有12个参数,包括任何指向返回值的指针参数。
  • 假设使用较新的编译器,例如gcc 11或更高版本。
显然,内联将使问题无关。
不同的方法是否会影响编译器进行内联的能力?
如果没有内联,这两种方法之间是否会有性能差异?
指向返回值的指针参数在函数参数中的位置是否会产生影响?无论是对编译器的内联能力还是对非内联性能是否有影响?
编辑了(a)以提高清晰度。

3
我认为你找出答案的唯一方法就是进行实验并观察。我完全相信在不同的编译器和平台上,内联的能力会因情况而异。 - 500 - Internal Server Error
我倾向于传递一个指向结构体的指针,让函数填充它并根据是否完全成功返回true/false。然后,我会让编译器来担心优化问题。 - Tim Randall
3
"Right now I'm interested in the relative efficiency of two of those methods:" belongs to Is premature optimization really the root of all evil?. - chux - Reinstate Monica
@RM:这些函数被频繁调用,通常在几分钟内达到数十亿次。我们使用了性能分析工具来定位热点,并且确定其中一些函数确实涉及其中。 - Underhill
在选项b)中,只传递了8个字节,并且没有进行任何复制操作。 - David C. Rankin
2个回答

4

这是 ABI 特定的。

Linux / x86-64 上,一个 struct 恰好包含两个字(例如两个指针、两个 intptr_t 或两个 long)会返回到两个寄存器中。这比例如 malloc 分配它要快得多,并且可能比调用者在调用栈上分配的两个字的 struct 更快(然后它很可能在某个快速 CPU 缓存 中;请记住,在最新的处理器上,缓存未命中可能需要数百纳秒,或者相当于一百个寄存器到寄存器整数加法机器指令所需的时间)

但是内联函数并不总是更快的。您还可以使用局部求值技术或 C++ 代码生成(例如 RefPerSys

使用最新的GCC编译器,还考虑将所有C或C++文件进行编译并进行链接时优化(例如-flto -O2


该项目使用了-O2优化选项。但是-flto会破坏它(目前正在调查原因)。 - Underhill
这将是一个本地结构,填充后返回。没有 malloc。我已经修改了问题以增加清晰度。 - Underhill
我接受这个答案。malloc的部分并不相关,但它提到了寄存器。这可能是我关注的情况中唯一会产生真正效果的优化。 - Underhill

2
我认为问题是:哪个更快(假设没有内联):
void fn(int *a, int *b, int *c) {
  *a = ...;
  *b = ...;
  ... etc.
}

vs.
void fn(struct foo *f) {
  f->a = ...;
  f->b = ...;
  ... etc.
}

在孤立的情况下,结构体变量会更快,因为它不需要从内存中加载单独的指针(在 x86 上,只能在寄存器中传递几个指针,其余的将被溢出到堆栈)。
但是,调用者上下文也很重要。如果调用者如下所示:
int a; double d1; int b; double d2; int c; ...
struct foo f;
fn(&f);
a = f->a;
b = f->b;
... etc.

然后,通过"unpack foo"代码,节省的效果将大部分被抵消。

但是,如果调用者是这样的:

struct foo f;
fn(&f);
if (f->a != 0) ...
int x = f->a + f->b;
... etc.

然后"解包"代码将不存在。


5
我可能错了,但听起来OP更像是在谈论从函数中_by value_返回结构体。 - Chris
@Chris 编译器很可能会将“按值返回”转换为“在堆栈上分配结构体并传递指针”的方式(NRVO)。 - Employed Russian
2
不一定。根据一些 ABI,足够小的结构体可能会通过(可能是多个)寄存器返回值。 - John Bollinger
1
@Chris:没错。类似于返回一个std::pair<>。 - Underhill
@John和ER:是的,这些结构体通常会很小 - 一般只有一个指针和一个整数。 - Underhill

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