Разница в производительности между структурами одинакового размера, но разным количеством членов - PullRequest
0 голосов
/ 23 февраля 2020

Предположим, у нас есть 2 структуры POD, состоящие только из целочисленных типов данных (включая перечисления и необработанные указатели).

struct A
{
  std::int64_t x = 0;
  std::int64_t y = 1;
};

struct B
{
  std::int64_t x = 0;
  std::int32_t y = 1;
  std::int32_t z = 2;
};

Обратите внимание, что A и B являются оба размером 128 бит. Давайте также предположим, что мы находимся на 64-битной архитектуре.

Конечно, если x, y и z не были целочисленными типами, стоимость копирования, перемещения, построения и уничтожения может отличаться между A и B в зависимости от деталей реализации элементов.

Но если мы предположим, что x, y и z являются только целыми типами, есть ли разница в стоимости между A и B в терминах:

  1. Строительство
  2. Копировать Строительство / Назначение
  3. Доступ к элементу (играет ли здесь какое-либо роль выравнивание?)

В частности, универсально ли дороже копирование и инициализация двух соседних 32-битных целых чисел, чем одиночное 64-битное целое число?

Или это что-то определенное c для флагов компилятора и оптимизации?

1 Ответ

2 голосов
/ 23 февраля 2020

Но если мы предположим, что x, y и z являются только целыми типами, есть ли разница в стоимости между A и B ...

При условии, что A и B являются тривиальными типами одинакового размера, не должно быть никакой разницы в стоимости создания и копирования. Это связано с тем, что современные компиляторы реализуют объединение хранилищ :

-fstore-merging

Выполнение слияния узких хранилищ с последовательными адресами памяти. Этот проход объединяет смежные хранилища непосредственных значений, более узких, чем слово, в меньшее количество более широких хранилищ, чтобы уменьшить количество инструкций. Это включено по умолчанию на -O2 и выше, а также -Os.

Пример кода:

#include <cstdint>

struct A {
  std::int64_t x = 0;
  std::int64_t y = 1;
};

struct B {
  std::int64_t x = 0;
  std::int32_t y = 1;
  std::int32_t z = 2;
};

A f0(std::int64_t x, std::int64_t y) {
    return {x, y};
}

B f1(std::int64_t x, std::int32_t y, std::int32_t z) {
    return {x, y, z};
}

void g0(A);
void g1(B);

void h0(A a) { g0(a); }
void h1(B b) { g1(b); }

Здесь создается сборка для построения и копирования :

gcc-9.2 -O3 -std=gnu++17 -march=skylake:

f0(long, long):
        mov     rax, rdi
        mov     rdx, rsi
        ret
f1(long, int, int):
        mov     QWORD PTR [rsp-16], 0
        mov     QWORD PTR [rsp-24], rdi
        vmovdqa xmm1, XMMWORD PTR [rsp-24]
        vpinsrd xmm0, xmm1, esi, 2
        vpinsrd xmm2, xmm0, edx, 3
        vmovaps XMMWORD PTR [rsp-24], xmm2
        mov     rax, QWORD PTR [rsp-24]
        mov     rdx, QWORD PTR [rsp-16]
        ret
h0(int, A):
        mov     rdi, rsi
        mov     rsi, rdx
        jmp     g0(A)
h1(int, B):
        mov     rdi, rsi
        mov     rsi, rdx
        jmp     g1(B)

clang-9.0 -O3 -std=gnu++17 -march=skylake:

f0(long, long):                                # @f0(long, long)
        mov     rdx, rsi
        mov     rax, rdi
        ret
f1(long, int, int):                               # @f1(long, int, int)
        mov     rax, rdi
        shl     rdx, 32
        mov     ecx, esi
        or      rdx, rcx
        ret
h0(int, A):                               # @h0(int, A)
        mov     rdi, rsi
        mov     rsi, rdx
        jmp     g0(A)                 # TAILCALL
h1(int, B):                               # @h1(int, B)
        mov     rdi, rsi
        mov     rsi, rdx
        jmp     g1(B)                 # TAILCALL

Обратите внимание, как обе структуры передаются в регистрах в h0 и h1.

Однако gcc не выполняет код для построения B, генерируя ненужные AVX инструкции. Подано сообщение об ошибке .

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...