我正在开发一个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