目录
使用 oneMKL API 进行两维实数到复数的FFT计算(示例)
问题简介
- 调用 oneMKL 相应 API 函数, 产生 2048 * 2048 个 随机单精度实数();
- 根据 1 产生的随机数据作为输入,实现两维 Real to complex FFT 参考代码;
- 根据 1 产生的随机数据作为输入, 调用 oneMKL API 计算两维 Real to complex FFT;
- 结果正确性验证,对 2 和 3 计算的两维 FFT 输出数据进行全数据比对(允许适当精度误差), 输出 “结果正确”或“结果不正确”信息;
- 平均性能数据比对(比如运行 1000 次),输出 FFT 参考代码平均运行时间和 oneMKL FFT 平均运行时间。
oneMKL介绍
oneMKL是英特尔发布的数学内核库,提供了高性能的线性代数、FFT和随机数生成等数学函数。该库支持常见的CPU架构和操作系统,包括Windows、Linux和macOS等,能够充分利用Intel CPU的优势,提供了高效且灵活的API接口,方便用户进行编程。同时,oneMKL还提供了Python和MATLAB等语言的接口,为用户提供了更便捷的编程方式。总之,oneMKL是一个高效、灵活且易于使用的数学库,为科学计算和工程计算应用提供了强大的支持。
问题简单分析
- 调用oneMKL相应API函数产生随机数: 使用oneMKL库中的随机数生成函数,例如vdRngUniform,来生成指定行列数的随机单精度实数数组。
- 实现两维Real-to-Complex FFT参考代码: 首先,将输入数据按行进行一维FFT计算。然后,对每一列的一维FFT结果进行进一步的一维FFT计算,得到最终的二维FFT结果。
- 调用oneMKL API计算两维Real-to-Complex FFT结果正确性验证: 使用oneMKL库中的FFT函数,如cufftPlanMany和cufftExecC2R,根据生成的随机数据计算两维Real-to-Complex FFT的结果。
- 对两维FFT输出数据进行全数据比对: 将参考代码得到的二维FFT结果与oneMKL计算的二维FFT结果进行逐个元素比较。可以使用适当的误差范围来容忍小的数值误差。
- 平均性能数据比对: 分别运行1000次参考代码和oneMKL FFT计算,并记录每次运行的时间。计算平均运行时间并进行比较,即可得到平均性能数据比对结果
代码实现
-
生成随机数据
float* inputData = new float[dataSize]; generateRandomData(inputData, dataSize);
-
分配内存用于输出结果
MKL_Complex8* refOutput = new MKL_Complex8[dataSize]; MKL_Complex8* mklOutput = new MKL_Complex8[dataSize];
-
生成指定大小的随机单精度实数数组
void generateRandomData(float* data, int size) { std::random_device rd; std::mt19937 gen(rd()); std::uniform_real_distribution<float> dis(-1.0f, 1.0f); for (int i = 0; i < size; i++) { data[i] = dis(gen); } }
-
使用 oneMKL API 进行两维实数到复数的FFT计算(示例)
void mklFFT(float* input, MKL_Complex8* output, int size) { MKL_LONG status; DFTI_DESCRIPTOR_HANDLE handle; status = DftiCreateDescriptor(&handle, DFTI_SINGLE, DFTI_REAL, 2, size, size); if (status != 0) { std::cout << "创建oneMKL FFT描述符失败。" << std::endl; return; } status = DftiSetValue(handle, DFTI_PLACEMENT, DFTI_NOT_INPLACE); if (status != 0) { std::cout << "设置oneMKL FFT的位置失败。" << std::endl; DftiFreeDescriptor(&handle); return; } status = DftiCommitDescriptor(handle); if (status != 0) { std::cout << "提交oneMKL FFT描述符失败。" << std::endl; DftiFreeDescriptor(&handle); return; } status = DftiComputeForward(handle, input, output); if (status != 0) { std::cout << "计算oneMKL FFT失败。" << std::endl; } DftiFreeDescriptor(&handle); }
-
两维实数到复数的FFT实现(参考代码)
void referenceFFT(float* input, MKL_Complex8* output, int size) { DFTI_DESCRIPTOR_HANDLE handle; MKL_LONG status; status = DftiCreateDescriptor(&handle, DFTI_SINGLE, DFTI_REAL, 2, size, size); if (status != 0) { std::cout << "创建参考FFT描述符失败。" << std::endl; return; } status = DftiSetValue(handle, DFTI_PLACEMENT, DFTI_NOT_INPLACE); if (status != 0) { std::cout << "设置参考FFT的位置失败。" << std::endl; DftiFreeDescriptor(&handle); return; } status = DftiCommitDescriptor(handle); if (status != 0) { std::cout << "提交参考FFT描述符失败。" << std::endl; DftiFreeDescriptor(&handle); return; } status = DftiComputeForward(handle, input, output); if (status != 0) { std::cout << "计算参考FFT失败。" << std::endl; } DftiFreeDescriptor(&handle); }
-
允许适当的精度误差下验证两个数组是否完全相等
bool verifyResult(MKL_Complex8* refOutput, MKL_Complex8* mklOutput, int size) { float epsilon = 1e-6f; // 允许的精度误差 for (int i = 0; i < size; i++) { float diffReal = std::abs(refOutput[i].real - mklOutput[i].real); float diffImag = std::abs(refOutput[i].imag - mklOutput[i].imag); if (diffReal > epsilon || diffImag > epsilon) { return false; } } return true; }
完整代码
#include <iostream>
#include <random>
#include <chrono>
#include <cmath>
#include <mkl.h>
// 生成指定大小的随机单精度实数数组
void generateRandomData(float* data, int size) {
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_real_distribution<float> dis(-1.0f, 1.0f);
for (int i = 0; i < size; i++) {
data[i] = dis(gen);
}
}
// 参考代码:两维实数到复数的FFT实现
void referenceFFT(float* input, MKL_Complex8* output, int size) {
DFTI_DESCRIPTOR_HANDLE handle;
MKL_LONG status;
status = DftiCreateDescriptor(&handle, DFTI_SINGLE, DFTI_REAL, 2, size, size);
if (status != 0) {
std::cout << "创建参考FFT描述符失败。" << std::endl;
return;
}
status = DftiSetValue(handle, DFTI_PLACEMENT, DFTI_NOT_INPLACE);
if (status != 0) {
std::cout << "设置参考FFT的位置失败。" << std::endl;
DftiFreeDescriptor(&handle);
return;
}
status = DftiCommitDescriptor(handle);
if (status != 0) {
std::cout << "提交参考FFT描述符失败。" << std::endl;
DftiFreeDescriptor(&handle);
return;
}
status = DftiComputeForward(handle, input, output);
if (status != 0) {
std::cout << "计算参考FFT失败。" << std::endl;
}
DftiFreeDescriptor(&handle);
}
// 使用 oneMKL API 进行两维实数到复数的FFT计算
void mklFFT(float* input, MKL_Complex8* output, int size) {
MKL_LONG status;
DFTI_DESCRIPTOR_HANDLE handle;
status = DftiCreateDescriptor(&handle, DFTI_SINGLE, DFTI_REAL, 2, size, size);
if (status != 0) {
std::cout << "创建oneMKL FFT描述符失败。" << std::endl;
return;
}
status = DftiSetValue(handle, DFTI_PLACEMENT, DFTI_NOT_INPLACE);
if (status != 0) {
std::cout << "设置oneMKL FFT的位置失败。" << std::endl;
DftiFreeDescriptor(&handle);
return;
}
status = DftiCommitDescriptor(handle);
if (status != 0) {
std::cout << "提交oneMKL FFT描述符失败。" << std::endl;
DftiFreeDescriptor(&handle);
return;
}
status = DftiComputeForward(handle, input, output);
if (status != 0) {
std::cout << "计算oneMKL FFT失败。" << std::endl;
}
DftiFreeDescriptor(&handle);
}
// 验证两个数组是否完全相等(允许适当的精度误差)
bool verifyResult(MKL_Complex8* refOutput, MKL_Complex8* mklOutput, int size) {
float epsilon = 1e-6f; // 允许的精度误差
for (int i = 0; i < size; i++) {
float diffReal = std::abs(refOutput[i].real - mklOutput[i].real);
float diffImag = std::abs(refOutput[i].imag - mklOutput[i].imag);
if (diffReal > epsilon || diffImag > epsilon) {
return false;
}
}
return true;
}
int main() {
int size = 2048;
int dataSize = size * size;
int numRuns = 1000; // 运行次数
// 生成随机数据
float* inputData = new float[dataSize];
generateRandomData(inputData, dataSize);
// 分配内存用于输出结果
MKL_Complex8* refOutput = new MKL_Complex8[dataSize];
MKL_Complex8* mklOutput = new MKL_Complex8[dataSize];
// 参考代码实现两维实数到复数的FFT
auto startRef = std::chrono::high_resolution_clock::now();
for (int i = 0; i < numRuns; i++) {
referenceFFT(inputData, refOutput, size);
}
auto endRef = std::chrono::high_resolution_clock::now();
std::chrono::duration<double> refDuration = (endRef - startRef) / numRuns;
// 使用 oneMKL API 进行两维实数到复数的FFT计算
auto startMkl = std::chrono::high_resolution_clock::now();
for (int i = 0; i < numRuns; i++) {
mklFFT(inputData, mklOutput, size);
}
auto endMkl = std::chrono::high_resolution_clock::now();
std::chrono::duration<double> mklDuration = (endMkl - startMkl) / numRuns;
// 验证结果正确性
bool resultCorrect = verifyResult(refOutput, mklOutput, dataSize);
// 输出结果
if (resultCorrect) {
std::cout << "结果正确" << std::endl;
}
else {
std::cout << "结果不正确" << std::endl;
}
std::cout << "参考代码平均运行时间: " << refDuration.count() << " 秒" << std::endl;
std::cout << "oneMKL 平均运行时间: " << mklDuration.count() << " 秒" << std::endl;
// 释放内存
delete[] inputData;
delete[] refOutput;
delete[] mklOutput;
getchar();
return 0;
}
结果展示
结语
本次是我第一次使用oneAPI数学内核库(oneMKL)接口,该比赛内容主要是调用oneMKL相关接口实现2D、FFT变换等,难度不高但要花一定的时间去查阅示例代码与环境配置。虽然只是简单的FFT并没有特别深入了解,但已经初步体会到oneMKL对英特尔处理器架构的能够充分利用的特性。综上所述,oneMKL是一款功能强大、易于使用、性能卓越的数学库,对于需要进行计算密集型任务的用户来说是一款值得推荐的工具。