引言
在数字信号处理领域,快速傅里叶变换(FFT)是一种极为重要的算法,它能够将时域信号转换为频域信号,广泛应用于音频处理、图像处理、通信等多个领域。前面几篇博客已经分享了一些CPU计算FFT的内容。随着硬件性能的不断提升,利用图形处理器(GPU)加速 FFT 计算成为提高处理效率的有效方式。今天,我们就来探讨一下如何在ELF2开发板上,借助VkFFT 库实现基于 GPU 的 FFT 计算。
VkFFT简介
VkFFT 是一个基于 Vulkan API 的快速傅里叶变换库。Vulkan 是新一代的跨平台图形和计算 API,它提供了高性能、低开销的设备访问,允许开发者直接控制 GPU 资源,实现更高效的并行计算。VkFFT 充分利用 Vulkan 的并行计算能力,将 FFT 计算任务分配到 GPU 的多个计算单元上同时执行,极大地提升了 FFT 计算的速度,特别适合处理大规模数据的 FFT 运算。
前面的博文介绍过RK3588 集成了 ARM Mali-G610 MP4 GPU,这款 GPU 具备强大的并行计算能力。要充分发挥GPU的计算能力,可以使用 OpenCL库。VkFFT 支持多种API,包括Vulkan/CUDA/HIP/OpenCL/Level Zero/Metal,所以也可以在RK3588平台上运行。
RK3588 上利用 VkFFT 实现 FFT 计算的步骤
下载与编译 VkFFT库
从 VkFFT 的官方代码仓库(GitHub - DTolm/VkFFT: Vulkan/CUDA/HIP/OpenCL/Level Zero/Metal Fast Fourier Transform library)下载最新版本的代码。在下载完成后,进入VkFFT 的源代码目录,创建一个build目录用于存放编译生成的文件。使用cmake工具生成编译配置文件,例如:
mkdir build
cd build
cmake -DCMAKE_C_COMPILER=aarch64-linux-gnu-gcc \
-DCMAKE_CXX_COMPILER=aarch64-linux-gnu-g++ -DVKFFT_BACKEND=3 \
-DOpenCL_INCLUDE_DIR=../../opencltest/OpenCL-Headers-2020.03.13 \
-D OpenCL_LIBRARY=../../opencltest/lib/libmali.so \
-DCMAKE_EXE_LINKER_FLAGS="-Wl,--allow-shlib-undefined" ..
上述命令会根据系统环境和 VkFFT的配置文件生成相应的 Makefile。然后,使用make命令进行编译:
make
编译完成后,会在build目录下生成 VkFFT测试程序,可以在开发板上测试效果。
VkFFT是一个只有头文件组成的库,编译后不会生成静态或动态库。
测试程序运行结果
下面是部分运行结果。
我也尝试了进行2048点的运算,结果如下:
root@elf2-buildroot:~# ./VkFFT_TestSuite -benchmark_vkfft -X 2048
arm_release_ver: g13p0-01eac0, rk_so_ver: 10
VkFFT System: 2048x1x1 Batch: 1 Buffer: 0 MB avg_time_per_step: 0.909 ms std_error: 0.268 num_iter: 1 benchmark: 17 scaled bandwidth: 0.1 real bandwidth: 0.1
这个时间明显是比前面的CPU测试要长的,这主要是因为2048点FFT太短了,它没法发挥GPU的优势,而CPU和GPU之间的数据传输和GPU调度的开销显得非常大。
编写 FFT 计算应用程序
我尝试使用DeepSeek生成程序,不过它不完全正确,下面是修改后的程序。程序中没有包括填充数据的部分。
#define VKFFT_BACKEND 3 // 设置为OpenCL后端
#include <vkFFT.h>
#include <CL/cl.h>
#include <iostream>
#include <vector>
#include <chrono>
int main() {
const uint64_t fftSize = 2048; // FFT点数
uint64_t bufferSize = fftSize * 2 * sizeof(float); // 复数数据大小
// OpenCL初始化
cl_platform_id platform;
cl_device_id device;
cl_context context;
cl_command_queue queue;
cl_int err;
// 获取第一个OpenCL平台
err = clGetPlatformIDs(1, &platform, NULL);
if (err != CL_SUCCESS) {
std::cerr << "获取OpenCL平台失败: " << err << std::endl;
return 1;
}
// 获取第一个GPU设备
err = clGetDeviceIDs(platform, CL_DEVICE_TYPE_GPU, 1, &device, NULL);
if (err != CL_SUCCESS) {
std::cerr << "获取OpenCL设备失败: " << err << std::endl;
return 1;
}
// 创建OpenCL上下文
context = clCreateContext(NULL, 1, &device, NULL, NULL, &err);
if (!context) {
std::cerr << "创建OpenCL上下文失败: " << err << std::endl;
return 1;
}
// 创建命令队列
queue = clCreateCommandQueue(context, device, 0, &err);
if (!queue) {
std::cerr << "创建命令队列失败: " << err << std::endl;
return 1;
}
// 配置VkFFT
VkFFTConfiguration config = {};
config.FFTdim = 1; // 1D FFT
config.size[0] = fftSize; // FFT点数
config.doublePrecision = 0; // 单精度
config.performR2C = 0; // 复数输入输出
config.normalize = 0; // 不进行归一化
config.bufferSize = &bufferSize;
// OpenCL相关配置
config.context = &context;
config.device = &device;
config.commandQueue = &queue;
VkFFTApplication app = {};
VkFFTResult res = initializeVkFFT(&app, config);
if (res != VKFFT_SUCCESS) {
std::cerr << "VkFFT初始化失败: " << res << std::endl;
return 1;
}
// 创建OpenCL缓冲区
cl_mem buffer = clCreateBuffer(context, CL_MEM_READ_WRITE,
bufferSize, NULL, &err);
if (!buffer) {
std::cerr << "创建缓冲区失败: " << err << std::endl;
return 1;
}
// 创建启动参数
VkFFTLaunchParams launchParams = {};
launchParams.buffer = &buffer;
launchParams.commandQueue = config.commandQueue;
// 执行FFT并计时
auto start = std::chrono::high_resolution_clock::now();
res = VkFFTAppend(&app, -1, &launchParams); // 正变换
if (res != VKFFT_SUCCESS) {
std::cerr << "FFT执行失败: " << res << std::endl;
return 1;
}
clFinish(queue); // 等待操作完成
auto end = std::chrono::high_resolution_clock::now();
// 计算耗时
auto duration = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
std::cout << "FFT执行时间: " << duration.count() << " 微秒" << std::endl;
// 清理资源
clReleaseMemObject(buffer);
deleteVkFFT(&app);
clReleaseCommandQueue(queue);
clReleaseContext(context);
return 0;
}
程序可以用下面的命令编译:
aarch64-linux-gnu-g++ -std=c++17 test.cpp -o vkfft_ocl -lmali \
-I../../opencltest/OpenCL-Headers-2020.03.13 \
-L../../opencltest/lib/ -Wl,--allow-shlib-undefined -I../vkFFT/
结语
在 RK3588 上利用 VkFFT 实现基于 GPU 的 FFT 计算,提供了一种新的计算方式。
然而,在实际应用中,也需要注意一些问题。例如数据传输的开销,在将数据从 CPU 内存传输到 GPU 内存以及将计算结果从 GPU 内存传输回 CPU 内存的过程中,会存在一定的时间开销,需要合理优化数据传输策略。
总的来说,VkFFT为在 RK3588 等具备强大 GPU 的设备上实现高效的 FFT 计算提供了有力的工具。通过合理的环境搭建、库使用和程序优化,我们能够充分发挥 GPU 的并行计算优势,提升数字信号处理应用的性能。如果你对 GPU 加速计算和 FFT 算法感兴趣,不妨尝试在 RK3588 上使用 VkFFT 进行实践,探索更多的应用可能性。
以上就是在RK3588 上利用VkFFT实现基于 GPU 的 FFT 计算的详细介绍。如果你在实践中有任何问题,或者想了解更多相关内容,欢迎在评论区留言交流。