英特尔 oneAPI 黑客松

英特尔 oneAPI 黑客松

快速傅里叶变换

快速傅里叶变换(Fast Fourier Transform, FFT),是快速计算序列的离散傅里叶变换(DFT)或其逆变换的方法。FFT 能够将计算 DFT 的复杂度从𝑂(𝑛2)降低到 𝑂(𝑛𝑙𝑜𝑔(𝑛)),其中𝑛为数据大小。
  FFT 算法的关键思想是利用信号的对称性和周期性,通过分而治之的策略来加速傅里叶变换的计算,从而大幅提高了计算率。通过将信号分解成多个子信号,然后逐步合并子信号的频谱,从而实现对整体频谱的计算。

使用oneMKL对FFT进行加速和优化

Intel oneMKL(Intel oneAPI Math Kernel Library)是一款高性能数学核心库,旨在为开发人员提供丰富的数学函数和优化算法,以加速各种科学、工程和数据分析应用的性能。oneMKL 是 Intel oneAPI 套件的一部分,适用于多种硬件架构,包括 Intel CPU、GPU 以及其他加速器。

问题简介

  1. 调用 oneMKL 相应 API 函数, 产生 2048 * 2048 个 随机单精度实数();
  2. 根据 1 产生的随机数据作为输入,实现两维 Real to complex FFT 参考代码;
  3. 根据 1 产生的随机数据作为输入, 调用 oneMKL API 计算两维 Real to complex FFT;
  4. 结果正确性验证,对 2 和 3 计算的两维 FFT 输出数据进行全数据比对(允许适当精度误差), 输出 “结果正确”或“结果不正确”信息;
  5. 平均性能数据比对(比如运行 1000 次),输出 FFT 参考代码平均运行时间和 oneMKL FFT 平均运行时间。

环境准备

参考Linux下Intel编译器oneAPI安装和链接MKL库编译_intel mkl 编译_XianJiaoMu的博客-CSDN博客这篇博客,在linux下安装最新版的MKL库,同时安装HPC toolkit,HPC包含C/C++ 和 Fortran的编译器。

实验代码

  1. 首先需要首先需要产生 2048 * 2048 个 随机单精度实数,可以通过调用oneMKL的随机数生成器vslNewStream生成
        float *input = (float*)malloc(width * height * sizeof(float));

        //生成单精度随机数
        VSLStreamStatePtr stream;
        vslNewStream(&stream, VSL_BRNG_MT19937, 1010);
        vsRngUniform(VSL_RNG_METHOD_UNIFORM_STD, stream, width * height, input, 0.0f, 1.0f);
  1. 然后调用fftw3.h中fftw3库的api创建fftw计划,并计算FFT变换的结果,同时记录一次计算所需的时间
 //FFTW输出数据
        fftwf_complex* fftw3_output = (fftwf_complex*)fftwf_malloc((width / 2 + 1) * height * sizeof(fftwf_complex));
        //创建FFTW计划
        fftwf_plan fftw3_plan = fftwf_plan_dft_r2c_2d(width, height, input, fftw3_output, FFTW_ESTIMATE);
        
        //fftw3计算
        struct timespec fftw3_start_time, fftw3_end_time;
        clock_gettime(CLOCK_MONOTONIC, &fftw3_start_time);
        fftwf_execute(fftw3_plan);
        clock_gettime(CLOCK_MONOTONIC, &fftw3_end_time);
        // 计算运行时间,单位为纳秒
        fftw3_time += (fftw3_end_time.tv_sec - fftw3_start_time.tv_sec) * 1000000000LL + (fftw3_end_time.tv_nsec - fftw3_start_time.tv_nsec);
  1. 接着调用 oneMKL API ,创建 oneMKL描述符,计算FFT变换的结果,同时记录一次计算所需的时间
//MKL输出数据
        MKL_LONG dim_sizes[2] = {width, height};
        MKL_Complex8* mkl_output = (MKL_Complex8*)malloc((width / 2 + 1) * height * sizeof(MKL_Complex8));
        //设置步长
        MKL_LONG input_stride[3] = {0, width, 1 };
        MKL_LONG output_stride[3] = {0, width / 2 + 1, 1};
        //设置oneMKL参数
        DFTI_DESCRIPTOR_HANDLE handle = NULL;
        DftiCreateDescriptor(&handle, DFTI_SINGLE, DFTI_REAL, 2, dim_sizes);
        DftiSetValue(handle, DFTI_PLACEMENT, DFTI_NOT_INPLACE); //设置非原地变换
        DftiSetValue(handle, DFTI_CONJUGATE_EVEN_STORAGE, DFTI_COMPLEX_COMPLEX); //设置共轭存储
        DftiSetValue(handle, DFTI_INPUT_STRIDES, input_stride);
        DftiSetValue(handle, DFTI_OUTPUT_STRIDES, output_stride);
        DftiCommitDescriptor(handle);

        //oneMKL计算
        struct timespec mkl_start_time, mkl_end_time;
        clock_gettime(CLOCK_MONOTONIC, &mkl_start_time);
        DftiComputeForward(handle, input, mkl_output);
        clock_gettime(CLOCK_MONOTONIC, &mkl_end_time);
        mkl_time += (mkl_end_time.tv_sec - mkl_start_time.tv_sec) * 1000000000LL + (mkl_end_time.tv_nsec - mkl_start_time.tv_nsec);
  1. 对使用fftw和oneMKL的输出进行比对,设定的精度误差允许范围为1e-6,分别比较二者计算FFT后的复数的实部和虚部之间的差异,超过该误差则认为结果不正确,全部在误差允许范围内则认为计算正确。
        //比较不同计算方法结果
        int notmatch_count = 0;
        for(int j = 0; j < (width / 2 + 1) * height; j++){
            float real_diff = fabs(fftw3_output[j][0] - mkl_output[j].real);
            float imag_diff = fabs(fftw3_output[j][1] - mkl_output[j].imag);
            if(real_diff > 1e-6 || imag_diff > 1e-6){
                notmatch_count++;
            }
        }

        if(notmatch_count > 0){
            printf("第%d轮计算结果不正确! %d \n", i, notmatch_count);
            not_correct_count++;
        }
  1. 设定循环运行一千次,比较平均运行时间,注意每轮循环结束时需要先释放资源
           //释放资源
           fftwf_destroy_plan(fftw3_plan);
           fftwf_free(fftw3_output);
           DftiFreeDescriptor(&handle);
           free(input);
           free(mkl_output);

完整代码如下:

#include<stdio.h>
#include <fftw3.h>
#include <mkl.h>
#include <time.h>
#include <math.h>

int main(){
    int width = 2048; //输入数据的宽度
    int height = 2048; //输入数据的高度
    long long fftw3_time = 0;
    long long mkl_time = 0;
    int iteration = 1000;
    int not_correct_count = 0;
    
    //运行1000次
    for(int i = 0; i < iteration; i++){
        float *input = (float*)malloc(width * height * sizeof(float));

        //生成单精度随机数
        VSLStreamStatePtr stream;
        vslNewStream(&stream, VSL_BRNG_MT19937, 1010);
        vsRngUniform(VSL_RNG_METHOD_UNIFORM_STD, stream, width * height, input, 0.0f, 1.0f);
        
        //FFTW输出数据
        fftwf_complex* fftw3_output = (fftwf_complex*)fftwf_malloc((width / 2 + 1) * height * sizeof(fftwf_complex));
        //创建FFTW计划
        fftwf_plan fftw3_plan = fftwf_plan_dft_r2c_2d(width, height, input, fftw3_output, FFTW_ESTIMATE);
        
        //fftw3计算
        struct timespec fftw3_start_time, fftw3_end_time;
        clock_gettime(CLOCK_MONOTONIC, &fftw3_start_time);
        fftwf_execute(fftw3_plan);
        clock_gettime(CLOCK_MONOTONIC, &fftw3_end_time);
        // 计算运行时间,单位为纳秒
        fftw3_time += (fftw3_end_time.tv_sec - fftw3_start_time.tv_sec) * 1000000000LL + (fftw3_end_time.tv_nsec - fftw3_start_time.tv_nsec);

        //MKL输出数据
        MKL_LONG dim_sizes[2] = {width, height};
        MKL_Complex8* mkl_output = (MKL_Complex8*)malloc((width / 2 + 1) * height * sizeof(MKL_Complex8));
        //设置步长
        MKL_LONG input_stride[3] = {0, width, 1 };
        MKL_LONG output_stride[3] = {0, width / 2 + 1, 1};
        //设置oneMKL参数
        DFTI_DESCRIPTOR_HANDLE handle = NULL;
        DftiCreateDescriptor(&handle, DFTI_SINGLE, DFTI_REAL, 2, dim_sizes);
        DftiSetValue(handle, DFTI_PLACEMENT, DFTI_NOT_INPLACE); //设置非原地变换
        DftiSetValue(handle, DFTI_CONJUGATE_EVEN_STORAGE, DFTI_COMPLEX_COMPLEX); //设置共轭存储
        DftiSetValue(handle, DFTI_INPUT_STRIDES, input_stride);
        DftiSetValue(handle, DFTI_OUTPUT_STRIDES, output_stride);
        DftiCommitDescriptor(handle);

        //oneMKL计算
        struct timespec mkl_start_time, mkl_end_time;
        clock_gettime(CLOCK_MONOTONIC, &mkl_start_time);
        DftiComputeForward(handle, input, mkl_output);
        clock_gettime(CLOCK_MONOTONIC, &mkl_end_time);
        mkl_time += (mkl_end_time.tv_sec - mkl_start_time.tv_sec) * 1000000000LL + (mkl_end_time.tv_nsec - mkl_start_time.tv_nsec);
        
        //比较不同计算方法结果
        int notmatch_count = 0;
        for(int j = 0; j < (width / 2 + 1) * height; j++){
            float real_diff = fabs(fftw3_output[j][0] - mkl_output[j].real);
            float imag_diff = fabs(fftw3_output[j][1] - mkl_output[j].imag);
            if(real_diff > 1e-6 || imag_diff > 1e-6){
                notmatch_count++;
            }
        }

        if(notmatch_count > 0){
            printf("第%d轮计算结果不正确! %d \n", i, notmatch_count);
            not_correct_count++;
        }
        
        //释放资源
        fftwf_destroy_plan(fftw3_plan);
        fftwf_free(fftw3_output);
        DftiFreeDescriptor(&handle);
        free(input);
        free(mkl_output);
    }
    
    if(not_correct_count == 0){
        printf("%d轮计算结果全部正确\n", iteration);
    }
    printf("FFTW3  FFT 平均运行时间:%lld 纳秒\n", fftw3_time / iteration);
    printf("OneMKL FFT 平均运行时间:%lld 纳秒\n", mkl_time / iteration);
}

测试步骤

测试步骤

  1. 编译链接

    使用命令生成可执行文件fft

     icc -qmkl fft.c -o fft
    
  2. 运行

    使用命令运行可执行文件fft,得到实验结果

    ./ fft
    

实验结果

请添加图片描述

感受

这次是我第一次了解到Intel的oneMKL库,使用下来感觉相较于其他的API上手简单,环境配置方便,相关文档也十分详细,使用者能够很快上手,同时其对于底层硬件和算法的优化,使得其具有很高的性能优势,以后在涉及到高性能数学计算时会优先考虑使用。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值