这不是询问 struct 填充/填充, struct 填充/填充指的是出于对齐目的而插入到 struct 中的任何未命名字节.

我有这个功能:

#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);
}

令我惊讶的是,尽管由于发出了多个andsar指令而启用了优化,但仍有GCC does not seem to use any SWAR for this条指令.

但我认为我可以像这样简单地实现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];
}

正如预期的那样,程序集实际上更短:Version 1 Version 2 Version 3(都是一样的)

  • 为什么GCC没有把前者优化为后者?有什么特别的原因吗?或者这只是一次错过的优化?
  • 单个字节参数的访问方式是否与struct/union中的字节不同?如果是,原因是什么?我的直觉告诉我,它们不应该是这样的,因为无论哪种方式,它们都位于当前堆栈帧中的已知位置.
  • 有什么理由这样做会比单独通过它们更慢呢?

我已经看过了:Passing many variables vs. passing struct,但这个问题更多地集中在比CPU字长大得多的大型 struct 上,而我的对象只有4个字节.这些也没有解决访问字内的各个字节的问题.

推荐答案

正如 comments 中指出的,您的版本在功能上并不相同,因为例如,第一个版本返回0,第二个版本返回2.您说l的值不会超过5,但编译器当然不能知道这一点,并且必须发出代码,为所有可能的输入提供正确的结果.(在 comments 中,您说您try 在断言l <= 5中添加__builtin_unreachable,这在原则上会使优化合法,但我仍然认为编译器很难找到它.)

但先不说这个...

无论哪种方式,它们都位于当前堆栈帧中的已知位置

您在x86-64上使用SysV ABI,其中前几个整数参数在堆栈上not传递,但在寄存器中传递.而且,单独的参数在单独的寄存器中传递,即使它们足够小,可以放在一个寄存器中.另一方面,8字节或更少的聚合( struct 或联合)在单个寄存器中传递.

(即使在x86-32上,它也会传递堆栈上的所有参数,SysV ABI要求将窄于32位的参数加宽并在单独的4字节堆栈槽中传递(以便可以使用push). 因此,在这种情况下,尽管在版本1中,字节在堆栈上的偏移量为known,但它们不是adjacent,因此我们仍然需要做更多的工作来将它们打包到一个寄存器中. https://godbolt.org/z/xqnndfGPr )

所以你在这里不是在比较苹果和苹果.该函数的联合版本可能看起来更高效.但是,如果它的调用者从三次单独的计算中获得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;
}

Try on godbolt

sum_2中,由于x86没有很好的方法来处理单个寄存器的add个不同字节或位域(除了通过al/ah等的低两个字节),我们需要额外的指令来解压缩到更多的寄存器中.

所以在回答你的标题问题时,是的,绝对可以有性能惩罚.

C++相关问答推荐

Pure Win32 C(++)-除了替换控件的窗口程序之外,还有其他方法可以在输入时禁用按钮吗?

为什么C语言允许你使用var =(struct NAME){

为什么可以在typedef之前使用typedef d struct 体?

如何在C客户端应用程序的ClientHello消息中添加自定义扩展?

二进制计算器与gmp

如何在c++中包装返回空*的函数

为什么我不能只在内存地址中添加一个int来寻址任何数组?

轮询libusb_pollfd struct 列表的正确方式是什么?

为什么memcpy进入缓冲区和指向缓冲区的指针工作相同?

FRIDA-服务器成为端口扫描的目标?

通过描述符查找文件路径时出现问题

理解bzip2的BZ2_解压缩函数中的状态重新分配

从不兼容的指针类型返回&&警告,但我看不出原因

我可以创建适用于不同endian的 colored颜色 struct 吗?

unions 的原子成员是个好主意吗?

在C中,为什么这个带有递增整数的main函数从不因溢出而崩溃?

C 错误:对 int 数组使用 typedef 时出现不兼容的指针类型问题

如何找出C中分配在堆上的数组的大小?

在 C/C++ 中原子按位与字节的最佳方法?

C语言程序流程解释