簡體   English   中英

長度為8的結構的效率,以及uint64_t

[英]Efficiency of structs of length 8, and uint64_t

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) {

等等

我的問題是關於在函數和賦值中的使用。 當我按值傳遞此結構時,編譯器(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 ? */

您無法確定union的大小與uint64_t相同。

這是由於在nodeid struct 打包 :編譯器通常會在struct成員之間插入空白。 有些編譯器允許您更改打包安排,但您的代碼將無法移植。

擁有一個uint8_t 數組會更安全:那么內存將是連續的。

然后編譯器將簡單地復制內存,因此您也可以使用nodeid_t作為函數返回類型。

你的第二個工作是重命名nodeid_t_t后綴在POSIX C中保留。

除了便攜性之外,如果您追求極致的低延遲,那么您就走在了正確的軌道上。 多年來我一直這樣做。
有幾點需要注意:
1.您的代碼(僅限字符)應該按原樣運行,因為char的對齊要求是1。
2.使用更寬的類型,您需要打包struct nodeid。 在gcc中,你使用__attribute__((packed)) 我認為MSVC使用#pragma push pack(1)...#pragma pop
3. Gcc曾經在這個區域有bug(比特字段之間的間隙,錯誤的對齊......)所以我強烈建議使用編譯時檢查,比如STATIC_ASSERT(sizeof(nodeid_t) == sizeof(uint64_t))
4.如果未填充8個字節中的某些字節,請確保在其中放置零或其他內容。 否則,您的比較等將使用隨機值。

這取決於您的架構。 但假設您使用的是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,那么您可能不需要浪費時間進行此優化,或者如果您希望比較有效,則可以按照您的需要進行操作。

對於這樣的事情,效率可能不會成為一個問題。

也就是說,這可能不會達到您的意圖:

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)將評估為true。

我現在傾向於將我的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到更復雜的結構。 這也意味着我不再需要工會了。

而不是所有這些花哨的步法,為什么不簡單地使用memcmp ,這適用於任何連續的數據類型可能實現為編譯內在因此應該快速並且絕對正確地避開嚴格的別名規則。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM