本节描述了往来加速器的部分专用数据传输,例如器件(结果)缓冲器的某些部分,此外还描述了允许单个加速器计算同时处理多个文件的不同文件传输样式。
I/O 子缓冲器的自定义传输
有时,您并不想把整个输出缓冲器都同步回主机,并且主机代码中无法预知您想要同步回主机的数据大小。压缩就是一个很好的例子。尤其是当压缩算法将多个区块压缩回同一个输出缓冲器,并且您只想把来自输出缓冲器的已压缩的区块同步回主机。如以下示例所示,当数据可用时,有个函数将用于精确控制同步回主机的数据,只需寄存该函数即可达成上述目的。
xfilter::custom_sync_outputs([=]()
{
auto fut = xfilter::sync_output<int>(outSz, chunks, 0);
fut.get();
for (int chunk = 0; chunk < chunks; ++chunk) {
xfilter::sync_output<int>(out, outSz[chunk], chunk * chunkSz);
}
});
custom_sync_outputs()
方法用于寄存回调函数,该函数将判定哪个或哪些缓冲器/子缓冲器将同步回主机。此方法将必须在 send_while
主体内部先于首次 compute()
调用之前加以调用。
custom_sync_outputs()
会禁用输出缓冲器的所有自动回传。在回调函数内部,用户定义的代码全权控制同步回的数据以及是否需要执行同步。以上示例同步回了一个 outSz
缓冲器。该缓冲器包含压缩区块的大小。sync_output
API 会返回 std::future
。在该 future 上调用 fut.get()
将使代码等待此同步完成。随后,代码将把所有区块及其精确大小一同传回主机。这些调用还将返回 future,但无需再为其执行同步。“System Compilation”(系统编译)运行时层将确保所有这些同步均完成后再启动对应的 receive_all
迭代。
文件传输模式
System Compilation 模式还支持轻松读取和写入文件,如 受支持的平台和启动示例 中的 file_filter_sc
所示。在 create_bufpool()
方法中可使用以下任意模式来启用此操作,如 VPP_ACC 类 API 中所述:
-
vpp::p2p
模式:文件通过点对点 PCIe 网桥传输至加速器。该值仅适用于支持 P2P 功能特性的平台,例如,已连接 smartSSD 的 U2 卡。 -
vpp::h2c
模式:文件通过 PCIe 从主机 CPU(已连接文件服务器)传输到卡。该值是适用于通过 PCIe 连接到主机 CPU 的大部分 Alveo 卡的标准值。
此简单文件传输开关(vpp::p2p
和 vpp::h2c
)可允许加速器设计跨平台移植。只需在托管数据文件的典型主机 CPU(已连接文件服务器)上使用软件仿真来为任意平台进行设计测试即可。但最终必须为支持 P2P 的特定平台(例如,连接到 smartSSD 的 U2)编译设计,从而无需更改设计源文件即可直接移植。
以下代码示例对此进行了演示:
auto inBP = my_acc::create_bufpool(vpp::input , P2P ? vpp::p2p : vpp::h2c);
my_acc::send_while([=]() {
if (P2P) o_flags |= O_DIRECT;
int fd = open(fnm, o_flags, s_flags);
DT* in = (DT*)xfilter::file_buf(inBP, fd, fsz);
my_acc::compute(in, ...);
...
基于 P2P
标志的值,此代码将对主机映射的 NVMe 器件文件执行点对点 (P2P) 传输,或者将执行主机文件到器件传输 (H2C)。如果执行 P2P 传输,那么此文件将从 NVMe 器件直接加载到器件存储器中,不通过主机执行任何数据传输。如果执行 H2C 传输,那么 System Compilation 运行时层将把此文件从主机自动传输到器件缓冲器(或者反之以执行输出)。
要启用 P2P,此文件打开时必须指定 O_DIRECT
标志,如以上示例所示。在 P2P 模式下,调用返回给 VPP_ACC::file_buf
的主机指针用于计算调用实参的句柄,无法对其执行读取或写入。
多文件缓冲器
如 VPP_ACC 类 API 中所述,您可以在调用 compute()
方法前对 file_buf
方法执行多次调用,以将多个文件或者多个文件段映射到单个器件缓冲器。以下代码示例显示了加速器在单次 compute()
调用内对多个文件中的一小部分同时执行处理的示例。
void* VPP_ACC::file_buf(VPP_BP bp, int fd, size_t sz, off_t fos = 0, off_t bos = 0)
xfilter::send_while([=, &total_out_size]()
{
static int iter = 0;
int* in;
// collect all "chunks" input files into one "in" buffer
for (int chunk = 0; chunk < chunks; ++chunk) {
std::stringstream nm;
nm << DATA << iter << '-' << chunk << ".orig";
int ifd = open(nm.str().c_str(), rd_o_flags);
assert(ifd > 2);
in = xfilter::file_buf<int>(inBP, ifd, chunkSz, 0, chunk * chunkSz);
}
// prepare output buffer to be able to hold all chunks
int* out = xfilter::file_buf<int>(outBP, 0, chunks * chunkSz, 0);
// output buffer to provide the actual filtered size of each chunk
int* outSz = xfilter::alloc_buf<int>(outSzBP, chunks);
....
xfilter::compute(chunks, chunkSz, in, out, outSz);
....
});
代码创建一个输入 (in
) 缓冲器,用于保存这些文件段,并创建一个输出 (out
) 缓冲器用于保存已处理的输出数据。在每次 send_while
迭代中,file_buf()
都会为与 in
缓冲器关联的每个文件提供 chunkSz
和读取偏移 (chunk*chunkSz
)。在后续调用 compute()
时,所有这些文件段都将写入输入器件缓冲器,或者从输出器件缓冲器中读取所有这些文件分段。
in =
xfilter::file_buf<...>
分配,以便将最后返回的指针分配给 in
。在编写应用代码时,务必遵循此规律进行操作。输出文件的定制传输
您也可以为文件缓冲器使用定制同步,其中输出缓冲器可同步到 custom_sync_outputs()
中的文件描述符。如 VPP_ACC 类 API 中所述,您可通过调用 sync_output_to_file()
方法来完成此操作,如以下示例所示。
VPP_ACC::sync_output_to_file(void* buf, int fd, size_t byte_sz,
off_t fd_byte_offset = 0,
off_t buf_byte_offset = 0);
在此例中,调用添加到 VPP_ACC::file_buf(bufPool, fd, sz)
的文件将不予自动同步。因此实际添加文件并不需要调用 file_buf
API,只有返回主机指针时才需调用。最好的方法是添加如下虚拟文件即可:VPP_ACC::file_buf(bufPool,0,0);
以下提供了代码片段示例:
my_acc::send_while([=]() {
DT* out = (DT*)my_acc::file_buf(outBP, 0, 0);
...
my_acc::custom_sync_outputs([=](){
...
auto fut = my_acc::sync_output_to_file(out, fd, sz, fd_offset, buf_offset);
...
});
...