C ++ Выровнять 5-байтовые структуры по кешированию - PullRequest
0 голосов
/ 21 февраля 2019

Я разрабатываю инфраструктуру совместной обработки CPU-FPGA, поэтому мне нужен полный контроль над выравниванием моих данных.У меня есть структура данных, которая требует только 5 байтов:

typedef struct __attribute__ ((packed))  {
    uint32_t dst;
    uint8_t weight;
} edg_t;

Мой интерфейс FPGA может читать со скоростью 1 кэш-строку (64 байта) за цикл (200 миллионов операций чтения в секунду).Для моей производительности крайне важно, чтобы я поместил как можно больше элементов в одну строку кэша, поэтому о заполнении структуры не может быть и речи.

5 байт: 12 элементов / чтение
8 байт: 8 элементов /read (padded)
padding -> снижение производительности в 1,5 раза

Однако у меня не может быть структуры, перекрывающей границу между строками кэша, которая требует, чтобы я строил логику на FPGA для постоянного сдвига считываемых данных.

Мое текущее решение при создании буфера выглядит следующим образом:

int num_elements = 1000;
int num_cachelines = num_elements / 12 + 1;

uint8_t* buffer = new uint8_t[num_cachelines * 64]
uint8_t* buf_ptr = buffer - 4;

for (int i = 0; i < num_elements; i++) {
    if (i % 12 == 0) buf_ptr += 4; //skip the last 4 bytes of each cache-line

    edg_t* edg_ptr = (edg_t*) buf_ptr;
    edg_ptr->dst = i; //example, I have random generators here
    edg_ptr->weight = i % 256;
    buf_ptr++;

}

Теперь все было хорошо, когда FPGA выполняла всю работу самостоятельно, теперь я хочу, чтобы FPGA и CPU работалисотрудничать.это означает, что ЦПУ теперь должен также читать буфер.

Мне было интересно, существует ли лучший способ, чтобы компилятор автоматически обрабатывал заполнение, или мне придется каждый раз вручную пропускать байты, какЯ сделал в буфере создания кода выше?

1 Ответ

0 голосов
/ 21 февраля 2019

Я предполагаю, что вы создадите эту структуру буфера один раз, а затем заполняете ее снова и снова для чтения ПЛИС (или наоборот).Если это так, этот макет должен работать:

constexpr size_t cacheline_size = 64;
constexpr size_t num_elements = 1000;

struct __attribute__ ((packed)) edg_t  {
    /*volatile*/ uint32_t dst;   // volatile if the FPGA writes too
    /*volatile*/ uint8_t weight;
};

constexpr size_t elements_per_cachline = cacheline_size/sizeof(edg_t);
constexpr size_t num_cachelines = num_elements / elements_per_cachline + 1;

struct alignas(cacheline_size) cacheline_t {
    std::array<edg_t, elements_per_cachline> edg;
    inline auto begin() { return edg.begin(); }
    inline auto end() { return edg.end(); }
};

struct cacheline_collection_t {
    std::array<cacheline_t, num_cachelines> cl;
    inline void* address_for_fpga() { return this; }
    inline auto begin() { return cl.begin(); }
    inline auto end() { return cl.end(); }
};

int main() {
    cacheline_collection_t clc;
    std::cout << "edg_t                 : "
       << alignof(edg_t) << " " << sizeof(clc.cl[0].edg[0]) << "\n";
    std::cout << "cacheline_t           : "
       << alignof(cacheline_t) << " " << sizeof(clc.cl[0]) << "\n";
    std::cout << "cacheline_collection_t: "
       << alignof(cacheline_collection_t) << " " << sizeof(clc) << "\n";

    // access
    for(auto& cl : clc) {
        for(auto& edg : cl) {
            std::cout << edg.dst << " " << (unsigned)edg.weight << "\n";
        }
    }
}

сборка @ godbolt выглядит хорошо.Внутренний цикл полностью встроен в 12 кодовых блоков, где он добавляет 5 к смещению rax для каждого блока.Затем он переходит к следующей кешлине (условно) за 3 операции:

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