C 将5个字节结构与Cacheline保持一致



我正在开发一个CPU-FPGA共同处理框架,因此我需要完全控制数据的对齐。我的数据结构仅需要5个字节:

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

我的FPGA接口可以以1个缓存线(64个字节)的读数(2亿读/秒)读取。对我的表现至关重要,我将尽可能多的元素塞入一个缓存线上,因此填充结构是不可能的。

5字节:12个元素/读取
8个字节:8个元素/读取(填充)
填充 -> 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合作。这意味着CPU现在也必须读取缓冲区。

我想知道是否存在一种更好的方法来使编译器自动处理填充物,或者我必须像上面的缓冲区创建代码中一样每次手动跳过字节?

我假设您将一次创建此缓冲液结构,然后一遍又一遍地填充它,以供FPGA读取(或vice-a-versa)。如果是这样,则该布局应该有效:

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个代码块中,其中每个块的rax偏移量增加了5个。然后在3个操作中转到下一个Cacheline(条件):

    add     rax, 64
    cmp     rax, rcx
    jne     .LBB0_1

最新更新