这不是询问结构填充/打包,它是指出于对齐目的插入到结构中的任何未命名字节。
我有这个功能:
#include <stdint.h>
uint8_t get_index(const uint8_t xs, const uint8_t zs, const uint8_t ys, const uint8_t l) {
return (xs >> l & 1) | (zs >> l & 2) | (ys >> l & 4);
}
令我惊讶的是,尽管由于发出了多个 and
和 sar
指令而启用了优化,但 GCC
似乎并未为此使用任何 SWAR。但我发现我可以像这样简单地实现 SWAR:
#include <stdint.h>
union Arg {
uint8_t b[3];
uint32_t u;
};
uint8_t get_index(union Arg arg, const uint8_t l) {
static const union Arg mask = {.b = {1, 2, 4}};
/* Using this instead of an integer constant makes the behavior not depend on endianness.
This will be optimized into the appropriate integer constant anyway. */
arg.u = arg.u >> l & mask.u;
return arg.b[0] | arg.b[1] | arg.b[2];
}
正如预期的那样,组件实际上更短:版本1为什么GCC无法将前者优化为后者?有什么特殊原因还是这只是一个错过的优化?
struct
union
内的字节有任何不同?如果是,为什么?我的直觉告诉我,它们不应该是这样,因为无论哪种方式,它们都位于当前堆栈帧中的已知位置。有什么理由这样做会比单独传递它们慢吗?,但这个问题更多地关注比 CPU 字大小大得多的大型结构,而我的对象只有 4 个字节。这些也没有解决访问单词内各个字节的问题。
get_index(0,0,1,7)
将在第一个版本中返回 0,而在第二个版本中返回 2。您说您的
l
值不会超过 5,但编译器当然无法知道这一点,并且必须发出为所有可能输入提供正确结果的代码。但是先把这个放在一边......
无论哪种方式,它们都位于当前堆栈帧中的已知位置
不是您使用的是具有 SysV ABI 的 x86-64,其中前几个整数参数
在堆栈上传递,而是在寄存器中传递。单独的参数会在单独的寄存器中传递,即使它们小到足以容纳一个寄存器。另一方面,8 个字节或更少的聚合(结构或联合)在单个寄存器中传递。 所以你在这里不是在比较苹果与苹果。该函数的联合版本可能看起来更有效。但是,如果其调用者从三个单独的计算中获得
x,z,y
的值,那么它们可能最终会出现在三个单独的寄存器中,因此调用者将必须做更多的工作才能将它们打包到一个寄存器中。您并没有真正节省计算量,只是将其外包给其他人。
在某些情况下,将参数打包到单个寄存器中肯定比将它们放在单独的寄存器中更糟糕。考虑一些简单的事情,比如:#include <stdint.h>
uint8_t sum(const uint8_t a, const uint8_t b, const uint8_t c) {
return a+b+c;
}
struct triple { uint8_t x,y,z; };
uint8_t sum_2(struct triple s) {
return s.x + s.y + s.z;
}
sum_2
中,由于x86没有一个好的方法来
添加单个寄存器的不同字节或位域(除了通过
al/ah
等的低两个字节),我们需要额外的指令来解压到更多寄存器中.所以回答你的标题问题,是的,绝对可能会出现性能损失。