长度为8的结构的效率,以及uint64_t

时间:2015-07-27 13:22:03

标签: c performance struct compiler-optimization pass-by-value

TLDR:8字节结构的处理效果与8字节uint64_t一样有效吗?

我有3个非常相似的数据结构。它们长6,7和8个字节。我想把它们全部放在uint64_t变量中。目标是比较和分配将非常有效。 (这些值在几个(大)树中用作键)。

示例:我为7字节长的数据结构定义了以下数据结构。

typedef struct {
  union {
    uint64_t raw;
    struct {
      uint8_t unused;
      uint8_t node_number;
      uint8_t systemid[SYSTEMID_LENGTH]; /* SYSTEMID_LENGTH is 6 bytes. */
    } nodeid;
  };
} nodeid_t;

我现在可以通过原始联盟成员快速分配和复制。

nodeid_t x, y;

x.raw = y.raw
if (x.raw > y.raw) {

Etc等。

我的问题是关于在功能和作业中的使用。 当我按值传递此结构时,编译器(gcc)是否会识别出这些结构长度为8个字节。因此,好像他们是int64_t?

示例::

之间是否存在效率/性能差异?
int64_t my_function();
nodeid_t my_function();

换句话说,gcc会使用1条指令将nodeid_t放在堆栈上,好像它是一个整数吗?或者它会创建一个循环,并逐个复制8个字节?这取决于-O优化吗?

同样的转让问题。

int64_t a, b;
nodeid_t x, y;

a = b; /* One machine instruction, I hope. */
x = y; /* Also one instruction, or will it do a loop ? */

6 个答案:

答案 0 :(得分:1)

您无法确定union的尺寸与uint64_t相同。

这是由于nodeid struct中的打包:编译器通常会在struct成员之间插入空白。有些编译器允许您更改打包安排,但您的代码将无法移植。

拥有uint8_t数组会更安全:那么内存将是连续的。

编译器只需在赋值时复制内存,因此您也可以使用nodeid_t作为函数返回类型。

您的第二项工作是重命名nodeid_t:POSIX C中保留_t个后缀。

答案 1 :(得分:1)

除了可移植性之外,如果您在最终的低延迟之后,您就会走上正轨。多年来我一直这样做 有几点需要注意:
1.您的代码(仅限字符)应该按原样运行,因为char的对齐要求是1.
2.对于更广泛的类型,您需要打包struct nodeid。在gcc中,您使用__attribute__((packed))执行此操作。我认为MSVC使用#pragma push pack(1)...#pragma pop 3. Gcc曾经在这个区域有错误(位字段之间的间隙,错误的对齐......)所以我强烈建议使用编译时检查,例如STATIC_ASSERT(sizeof(nodeid_t) == sizeof(uint64_t))
4.如果未填充8个字节中的某些字节,请确保在其中放置零或其他内容。否则你的比较等会使用随机值。

答案 2 :(得分:1)

这取决于您的架构。但假设您使用x86_64(这是最有可能的),您不需要为复制和函数参数进行联合黑客攻击(您仍需要它进行比较)。

struct foo {
    char a;
    char b;
    short c;
    int d;
};

void
foo_copy(struct foo *a, struct foo *b)
{
    *a = *b;
}


extern void bar(struct foo a);
void
foo_value(void)
{
    struct foo f = { .a = 1 };
    bar(f);
}
$ cc -fomit-frame-pointer -O2 -S foo.c
$ cat foo.s
[... cleaned up ...]
_foo_copy:                              ## @foo_copy
    movq    (%rsi), %rax
    movq    %rax, (%rdi)
    retq

_foo_value:                             ## @foo_value
    movl    $1, %edi
    jmp _bar                    ## TAILCALL

不同的体系结构将有不同的要求,例如严格的对齐体系结构,除非ABI需要比通常的对齐更大,否则无法进行复制。其他ABI可能对结构有不同的调用约定。所以这一般很难回答。但是,如果您使用的是x86_64,那么您可能要么不需要浪费时间进行此优化,要么您希望比较效率高,这可能会像您想要的那样工作。

答案 3 :(得分:0)

对于这样的事情,效率可能不会成为一个问题。

那就是说,这可能不符合你的意图:

if (x.raw > y.raw) {

如果您在具有little-endian体系结构的计算机上运行,​​则首先存储最低有效字节。如果是这种情况,那么如果你有这个:

x.nodeid.systemid[0] = 1;
x.nodeid.systemid[1] = 2;
y.nodeid.systemid[0] = 2;
y.nodeid.systemid[1] = 1;

然后(x.raw > y.raw)会评估为真。

答案 4 :(得分:0)

我现在倾向于将我的3个数据结构定义为typedef uint64_t。

typedef uint64_t isis_simple_item_t;
typedef struct isis_complex_item_t {
  byte_t unused;
  byte_t node_number;
  byte_t systemid[ISIS_SYSTEMID_SIZE];
};

byte_t number;
isis_simple_item_t nodeid;

number = ((isis_complex_item) nodeid).node_number;

通过这种方式,我可以快速进行比较,分配,函数返回,函数参数值等。

然后当我需要访问结构中的一个成员时,发生的情况要少得多,我将使用包装函数。在其中使用强制转换,从uint64_t到更复杂的结构。这也意味着我不再需要工会了。

答案 5 :(得分:0)

而不是所有这些花哨的步法,为什么不简单地使用memcmp,这适用于任何连续的数据类型可能实现为编译内在,所以应该快速并且绝对正确地避开严格的别名规则。