数据传输
本节介绍数据传输的相关接口、注意事项,并给出示例代码。
接口调用流程
数据传输的关键接口调用流程如下:
- 申请内存。
- Host上的内存,可以使用AscendCL提供的aclrtMallocHost接口申请内存,也可以用C++标准库中的new、malloc接口申请内存。
- aclrtMallocHost会尝试申请物理地址连续的内存,后续在Host与Device数据交互时性能更优。调用aclrtMallocHost接口后、使用内存前,建议先调用aclrtMemset接口初始化内存,清除内存中的随机数。
- 若调用malloc接口,在调用malloc接口后、使用内存前,需调用memset初始化内存,清除内存中的随机数。
- Device上的内存,使用AscendCL提供的aclrtMalloc接口申请内存。如果涉及媒体数据处理(例如,图片解码、缩放等)时,需使用acldvppMalloc或hi_mpi_dvpp_malloc接口申请内存。
- Host上的内存,可以使用AscendCL提供的aclrtMallocHost接口申请内存,也可以用C++标准库中的new、malloc接口申请内存。
- 将数据读入内存。
由用户自行管理数据读入内存的实现逻辑。
- 通过内存复制实现数据传输。数据传输可以通过内存复制的方式实现,分为同步内存复制、异步内存复制:
- 同步内存复制:调用aclrtMemcpy接口。
- 异步内存复制:调用aclrtMemcpyAsync接口,再调用aclrtSynchronizeStream接口实现Stream内任务的同步等待。
- 对于Host内的数据传输、Device内的数据传输、Host与Device之间的数据传输,可以调用内存复制的接口实现,也可以直接通过指针传递数据。
- 调用同步或异步内存复制接口时,支持以下类型的复制(可单击链接查看对应类型的内存复制示例代码):

Ascend RC场景下,不涉及Host上的内存申请、Host内的数据传输、Host与Device之间的数据传输。
- 若应用程序中区分申请Host内存或Device内存的接口,例如使用C++标准库的接口或aclrtMallocHost接口申请Host内存、使用aclrtMalloc接口申请Device内存时:
需先调用aclrtGetRunMode接口获取软件栈的运行模式,当查询结果为ACL_HOST,则数据传输时涉及申请Host上的内存;当查询结果为ACL_DEVICE,则数据传输时不涉及申请Host上的内存,仅需申请Device上的内存。该种方式多一些代码逻辑的判断,不需要由用户处理Device上的内存对齐。在Device上运行应用的场景,该种方式少一些内存复制的步骤,性能较好。
- 若应用程序中不区分申请Host内存或Device内存的接口,统一使用aclrtMallocHost接口(该接口支持申请Host或Device内存),AscendCL内部会根据软件栈的运行模式自行判断运行时申请的是Host内存还是Device内存:
无需调用aclrtGetRunMode接口获取软件栈的运行模式。该种方式代码逻辑相比前一种简单,但需由用户处理Device上的内存对齐。
Host内的数据传输
当前支持调用aclrtMemcpy接口执行同步Host内的内存复制任务,不支持调用aclrtMemcpyAsync接口执行异步Host内的内存复制功能(对应ACL_MEMCPY_HOST_TO_HOST类型),否则接口返回报错ACL_ERROR_RT_FEATURE_NOT_SUPPORT。
调用接口后,需增加异常处理的分支,并记录报错日志、提示日志,此处不一一列举。以下是关键步骤的代码示例,不可以直接拷贝编译运行,仅供参考。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | // 1. 申请内存
uint64_t size = 1 * 1024 * 1024;
void* hostPtrA = NULL;
void* hostPtrB = NULL;
aclrtMallocHost(&hostPtrA, size);
aclrtMallocHost(&hostPtrB, size);
// 2. 申请内存后,可向内存中读入数据,该自定义函数ReadFile由用户实现
ReadFile(fileName, hostPtrA, size);
// 3. 内存复制,可以选择同步
// 同步内存复制,hostPtrA表示Host上源内存地址指针,hostPtrB表示Host上目的内存地址指针,size表示内存大小
aclrtMemcpy(hostPtrB, size, hostPtrA, size, ACL_MEMCPY_HOST_TO_HOST);
// 4. 使用完内存中的数据后,需及时释放资源
aclrtFreeHost(hostPtrA);
aclrtFreeHost(hostPtrB);
// ......
|
从Host到Device的数据传输
调用接口后,需增加异常处理的分支,并记录报错日志、提示日志,此处不一一列举。以下是关键步骤的代码示例,不可以直接拷贝编译运行,仅供参考。
- 同步内存复制
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
// 1. 申请内存 uint64_t size = 1 * 1024 * 1024; void* hostPtrA = NULL; void* devPtrB = NULL; aclrtMallocHost(&hostPtrA, size); aclrtMalloc(&devPtrB, size, ACL_MEM_MALLOC_HUGE_FIRST); // 2. 申请内存后,可向内存中读入数据,该自定义函数ReadFile由用户实现 ReadFile(fileName, hostPtrA, size); // 3. 内存复制,可以选择同步 // 同步内存复制,hostPtrA表示Host上源内存地址指针,devPtrB表示Device上目的内存地址指针,size表示内存大小 aclrtMemcpy(devPtrB, size, hostPtrA, size, ACL_MEMCPY_HOST_TO_DEVICE); // 4. 使用完内存中的数据后,需及时释放资源 aclrtFreeHost(hostPtrA); aclrtFree(devPtrB); // ......
- 异步内存复制Host内存需通过aclrtMallocHost申请,否则异步内存复制接口不会报错,但是在实际执行到相关业务时会发生不可预知的错误。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
// 1. 申请内存 uint64_t size = 1 * 1024 * 1024; void* hostAddr = NULL; void* devAddr = NULL; aclrtMallocHost(&hostAddr, size); aclrtMalloc(&devAddr, size, ACL_MEM_MALLOC_HUGE_FIRST); // 2. 异步内存复制 aclrtStream stream = NULL; aclrtCreateStream(&stream); // 申请内存后,可向内存中读入数据,该自定义函数ReadFile由用户实现 ReadFile(fileName, hostAddr, size); aclrtMemcpyAsync(devAddr, size, hostAddr, size, ACL_MEMCPY_HOST_TO_DEVICE, stream); aclrtSynchronizeStream(stream); // 3. 释放资源 aclrtDestroyStream(stream); aclrtFreeHost(hostAddr); aclrtFree(devAddr); // ......
从Device到Host的数据传输
调用接口后,需增加异常处理的分支,并记录报错日志、提示日志,此处不一一列举。以下是关键步骤的代码示例,不可以直接拷贝编译运行,仅供参考。
- 同步内存复制
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
// 1. 申请内存 uint64_t size = 1 * 1024 * 1024; void* devPtrA = NULL; void* hostPtrB = NULL; aclrtMalloc(&devPtrA, size, ACL_MEM_MALLOC_HUGE_FIRST); aclrtMallocHost(&hostPtrB, size); // 2. 申请内存后,可向内存中读入数据,该自定义函数ReadFile由用户实现 ReadFile(fileName, devPtrA, size); // 3. 内存复制,可以选择同步 // 同步内存复制,devPtrA表示Device上源内存地址指针,hostPtrB表示Host上目的内存地址指针,size表示内存大小 aclrtMemcpy(hostPtrB, size, devPtrA, size, ACL_MEMCPY_DEVICE_TO_HOST); // 4. 使用完内存中的数据后,需及时释放资源 aclrtFree(devPtrA); aclrtFreeHost(hostPtrB); // ......
- 异步内存复制Host内存需通过aclrtMallocHost申请,否则异步内存复制接口不会报错,但是在实际执行到相关业务时会发生不可预知的错误。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
// 1. 申请内存 uint64_t size = 1 * 1024 * 1024; void* hostAddr = NULL; void* devAddr = NULL; aclrtMallocHost(&hostAddr, size); aclrtMalloc(&devAddr, size, ACL_MEM_MALLOC_HUGE_FIRST); // 2. 申请内存后,可向内存中读入数据,该自定义函数ReadFile由用户实现 ReadFile(fileName, devAddr, size); // 3. 异步内存复制 aclrtStream stream = NULL; aclrtCreateStream(&stream); aclrtMemcpyAsync(hostAddr, size, devAddr, size, ACL_MEMCPY_DEVICE_TO_HOST, stream); aclrtSynchronizeStream(stream); // 4. 释放资源 aclrtDestroyStream(stream); aclrtFreeHost(hostAddr); aclrtFree(devAddr); // ......
一个Device内的数据传输
调用接口后,需增加异常处理的分支,并记录报错日志、提示日志,此处不一一列举。以下是关键步骤的代码示例,不可以直接拷贝编译运行,仅供参考。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | // 1. 申请内存
uint64_t size = 1 * 1024 * 1024;
void* devPtrA = NULL;
void* devPtrB = NULL;
aclrtMalloc(&devPtrA, size, ACL_MEM_MALLOC_HUGE_FIRST);
aclrtMalloc(&devPtrB, size, ACL_MEM_MALLOC_HUGE_FIRST);
// 2. 申请内存后,可向内存中读入数据,该自定义函数ReadFile由用户实现
ReadFile(fileName, devPtrA, size);
// 3. 内存复制,可以选择同步或异步
// 同步内存复制,devPtrA表示Device上源内存地址指针,devPtrB表示Device上目的内存地址指针,size表示内存大小
aclrtMemcpy(devPtrB, size, devPtrA, size, ACL_MEMCPY_DEVICE_TO_DEVICE);
// 异步内存复制
// 显式创建一个Stream
aclrtStream stream;
aclrtCreateStream(&stream);
aclrtMemcpyAsync(devPtrB, size, devPtrA, size, ACL_MEMCPY_DEVICE_TO_DEVICE, stream);
aclrtSynchronizeStream(stream);
// 4. 使用完内存中的数据后,需及时释放资源
aclrtDestroyStream(stream);
aclrtFree(devPtrA);
aclrtFree(devPtrB);
// ......
|
两个Device间的数据传输

注意点说明:
- 可使用aclrtDeviceCanAccessPeer接口查询两个Device之间是否支持内存复制,若支持,需调用两次aclrtDeviceEnablePeerAccess接口使能两个Device之间的内存复制功能(例如,调用一次aclrtDeviceEnablePeerAccess接口使能Device 0到Device 1的内存复制,再调用一次aclrtDeviceEnablePeerAccess接口使能Device 1到Device 0的内存复制),再调用aclrtMemcpy接口(同步接口)或aclrtMemcpyAsync接口(异步接口)通过内存复制的方式实现数据传输。
- 当前仅支持同一个PCIe Switch内Device之间的内存复制。
- 仅支持同一个进程内的同一个线程或不同线程间的Device之间的内存复制,不支持不同进程间Device之间的内存复制。
调用接口后,需增加异常处理的分支,并记录报错日志、提示日志,此处不一一列举。以下是关键步骤的代码示例,不可以直接拷贝编译运行,仅供参考。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 | int main(int argc, const char *argv[])
{
// AscendCL初始化
auto ret = aclInit(NULL);
int32_t canAccessPeer = 0;
// 查询Device 0和Device 1之间是否支持内存复制
ret = aclrtDeviceCanAccessPeer(&canAccessPeer, 0, 1);
// 1表示支持内存复制
if (canAccessPeer == 1) {
// ************************************************************
// Device 0下的操作
ret = aclrtSetDevice(0);
ret = aclrtDeviceEnablePeerAccess(1, 0);
void *dev0;
ret = aclrtMalloc(&dev0, 10, ACL_MEM_MALLOC_HUGE_FIRST_P2P);
ret = aclrtMemset(dev0, 10, 1, 10);
......
// ************************************************************
// Device 1下的操作,使能当前Device(Device 1)到指定Device(Device 0)的内存复制,当前Device通过aclrtSetDevice接口设置,指定Device在aclrtDeviceEnablePeerAccess接口的第一个参数指定
ret = aclrtSetDevice(1);
ret = aclrtDeviceEnablePeerAccess(0, 0);
void *dev1;
ret = aclrtMalloc(&dev1, 10, ACL_MEM_MALLOC_HUGE_FIRST_P2P);
ret = aclrtMemset(dev1, 10, 0, 10);
// 执行复制,将Device 0上的内存数据复制到Device 1上
ret = aclrtMemcpy(dev1, 10, dev0, 10, ACL_MEMCPY_DEVICE_TO_DEVICE);
ret = aclrtResetDevice(1);
......
// ************************************************************
// ************************************************************
// Device 0下的操作,调用aclrtResetDevice接口释放Device 0的资源
ret = aclrtSetDevice(0);
ret = aclrtResetDevice(0);
......
// ************************************************************
printf("P2P copy success\n");
} else {
printf("current device doesn't support p2p feature\n");
}
// AscendCL去初始化
aclFinalize();
return 0;
}
|