提问人:BobDeTunis 提问时间:2/6/2023 最后编辑:BobDeTunis 更新时间:2/6/2023 访问量:151
结构变量:按值传递与按指针传递给函数
Struct variable passed by value vs. passed by pointer to a function
问:
假设我有以下结构:
typedef struct s_tuple{
double x;
double y;
double z;
double w;
} t_tuple;
假设我有以下两个函数:
t_tuple tuple_sub_values(t_tuple a, t_tuple b)
{
a.x -= b.x;
a.y -= b.y;
a.z -= b.z;
a.w -= b.w;
return (a);
}
t_tuple tuple_sub_pointers(t_tuple *a, t_tuple *b)
{
t_tuple c;
c.x = a->x - b->x;
c.y = a->y - b->y;
c.z = a->z - b->z;
c.w = a->w - b->w;
return (c);
}
函数之间会有性能差异吗?其中一个比另一个好吗? 基本上,当所有结构元素都被调用时,按值传递与按指针传递的优缺点是什么?
编辑:完全改变了我的结构和功能,以给出一个更精确的例子 我找到了这个与我的问题相关的帖子,但是为了C++:https://stackoverflow.com/questions/40185665/performance-cost-of-passing-by-value-vs-by-reference-or-by-pointer#:~:text=In%20short%3A%20It%20is%20almost,reference%20parameters%20than%20value%20parameters。
上下文:在这个例子中,我的结构并不大,但我正在编写一个光线追踪器,一些大小约为 100B 的结构可以被调用数百万次,所以我想尝试优化这些调用。我的结构有点混乱,所以在这里复制它们会很混乱,这就是为什么我试图在一种一般示例中提出我的问题。
答:
在性能方面,这很可能是特定于实现的,原因与本文相去甚远,但最有可能的是,在最坏的情况下,我们谈论的是微秒。现在说到利弊:
按值传递只会给你一个该结构的副本,并且修改将只是本地的。换句话说,您的函数将收到结构的全新副本,并且它只能修改该副本。
相比之下,通过引用传递使您能够直接从函数修改给定的结构,并且经常在需要从函数返回多个值时出现。
完全由您决定选择适合您情况的哪一个。但要添加一些额外的帮助:
- 按引用传递将减少函数调用开销,因为您不必从头开始将 32 个字节复制到新函数。如果您计划保持较低的内存占用量,如果您计划多次调用该函数,它也将有很大帮助。为什么?因为您无需为这些调用创建多个不同的结构,只需告诉每个调用重用相同的结构即可。这主要出现在游戏中,其中结构可能有数千字节大。
评论
进入问题的核心:为了获得最佳的 arg 传递/值返回性能,您基本上希望遵循平台的 ABI,以尝试确保事物在寄存器中并保留在寄存器中。如果它们不在寄存器中,或者不能保留在寄存器中,那么通过指针传递大于指针大小的数据可能会节省一些复制(除非复制无论如何都需要在被调用者中完成:对于编码器来说,实际上可能比 }' 好一点)。void pass_copy(struct large x){ use(&x); }
void pass_copy2(struct large const*x){ struct large cpy=*x; use(&cpy);
例如,sysv x86-64 ABI 的具体规则有点复杂(请参阅调用约定一章)。 但一个简短的版本可能是:args/return-vals 通过寄存器,只要它们的类型“足够简单”并且适当的参数传递寄存器可用(6 用于整数值,6 用于双精度)。最多两个 8 字节的结构可以通过寄存器(作为参数或返回值),前提是它们“足够简单”。
假设你的 double 已经加载到寄存器中(或者没有聚合到你可以指向被调用方的寄存器中),将它们传递到 x86-64 SysV ABI 的最有效方法是单独传递或通过每个两个 double 的结构,但你仍然需要通过内存返回它们,因为 ABI 只能容纳带有寄存器的两个双精度 retvals, 不是 4 双 retvals。如果返回 fourdouble,编译器将在调用方中堆栈分配内存,并将指向它的指针作为隐藏的第一个参数传递,然后返回指向分配的内存的指针(在后台)。更灵活的方法是不返回如此大的聚合,而是显式传递指向要填充的结构的指针。这样,结构可以位于您想要的任何位置(而不是由编译器自动分配到堆栈上)。t_tuples
所以像这样
void tuple_sub_values(t_tuple *retval,
t_twodoubles a0, t_twodoubles a1,
t_twodoubles b0, t_twodoubles b1);
将是一个更好的 API,用于避免 x86-64 SysV ABI(Linux、MacOS、BSD......
如果您的测量结果显示代码大小节省/性能提升对您来说是值得的,您可以将其包装在一个内联函数中,该函数将执行结构拆分。
评论
const