2012-04-17 3 views
1

Matlab의 FFT는 계산을 수행하는 스레드 수를 선택할 수 없습니다 (http://stackoverflow.com/questions/9528833/matlabs-fftn-gets-slower-with-multithreading).). 기본적으로 독립 실행 형 matlab에있는 모든 코어를 사용합니다. 그러나 클러스터에서는 각 작업자가 기본적으로 단일 CPU로 시작됩니다. 당신은 더 많은 코어 (maxNumCompThreads 기능)와 함께 작동하도록 할 수 있습니다. 이것은 대수 연산과 완벽하게 작동하지만 FFT 함수는 (이상하게도?) 단일 코어로 남아 있습니다. 필자는 fftw 라이브러리 (matlab처럼)를 사용하여 원하는 코어 수와 함께 fft를 계산하는 mex 파일을 작성했습니다. 그러나 FFTW_ESTIMATE 플래너 (Matlab의 기본 설정)와 명확한 지혜를 사용하여 코드를 비교하려고하면 내 코드가 Matlab fft보다 3-4 배 느려집니다. 여기 FFTW를 Matlab FFT로 최적화

내가 MEX 사용 코드 (2 차원 FFT 적용 FFT2mx 이름)된다

#include <stdlib.h> 
#include <stdio.h> 
#include <mex.h> 
#include <matrix.h> 
#include <math.h> 
#include </home/nicolas/Code/C/lib/include/fftw3.h>  
void FFTNDSplit(int NumDims, const int N[], double *XReal, double *XImag, double *YReal, double *YImag, int Sign) 
    { 
     fftw_plan Plan; 
     fftw_iodim Dim[NumDims]; 
     int k, NumEl; 
     for(k = 0, NumEl = 1; k < NumDims; k++) 
     { 
     Dim[NumDims - k - 1].n = N[k]; 
     Dim[NumDims - k - 1].is = Dim[NumDims - k - 1].os = (k == 0) ? 1 : (N[k-1] * Dim[NumDims-k].is); 
     NumEl *= N[k]; 
     } 

     //fftw_import_wisdom_from_filename("/home/nicolas/wisdom/wis"); 

     if(!(Plan = fftw_plan_guru_split_dft(NumDims, Dim, 0, NULL, XReal, 
              XImag, YReal, YImag, FFTW_ESTIMATE))) 
     mexErrMsgTxt("FFTW3 failed to create plan."); 

     if(Sign == -1) 
     fftw_execute_split_dft(Plan, XReal, XImag, YReal, YImag); 
     else 
     { 
     fftw_execute_split_dft(Plan, XImag, XReal, YImag, YReal); 
     } 

     //if(!fftw_export_wisdom_to_filename("/home/nicolas/wisdom/wis")) 
     // mexErrMsgTxt("FFTW3 failed to save wisdom."); 

     fftw_destroy_plan(Plan); 
     return; 
    } 


    void mexFunction(int nlhs, mxArray *plhs[], 
        int nrhs, const mxArray *prhs[]) 
    { 

     int i, j,numCPU; 
     int NumDims; 
     const mwSize *N; 

     if (nrhs != 2) { 
      mexErrMsgIdAndTxt("MATLAB:FFT2mx:invalidNumInputs", 
        "Two input argument required."); 
     } 

     if (!mxIsDouble(prhs[0])) { 
      mexErrMsgIdAndTxt("MATLAB:FFT2mx:invalidNumInputs", 
        "Array must be double"); 
     } 

     numCPU = (int) mxGetScalar(prhs[1]); 
     if (numCPU > 8) { 
      mexErrMsgIdAndTxt("MATLAB:FFT2mx:invalidNumInputs", 
        "NumOfThreads < 8 requested"); 
     } 


     /*if (!mxIsComplex(prhs[0])) { 
      mexErrMsgIdAndTxt("MATLAB:FFT2mx:invalidNumInputs", 
        "Array must be complex"); 
     }*/ 

     NumDims = mxGetNumberOfDimensions(prhs[0]); 
     N = mxGetDimensions(prhs[0]); 

     plhs[0] = mxCreateDoubleMatrix(0, 0, mxCOMPLEX); 
     mxSetDimensions(plhs[0], N, NumDims); 
     mxSetData(plhs[0], mxMalloc(sizeof(double) * mxGetNumberOfElements(prhs[0]))); 
     mxSetImagData(plhs[0], mxMalloc(sizeof(double) * mxGetNumberOfElements(prhs[0]))); 

     fftw_init_threads(); 
     fftw_plan_with_nthreads(numCPU); 

     FFTNDSplit(NumDims, N, (double *) mxGetPr(prhs[0]), (double *) mxGetPi(prhs[0]), 
       mxGetPr(plhs[0]), mxGetPi(plhs[0]), -1); 

    } 

연관된 MATLAB 코드 :

function fft2mx(X,NumCPU) 

FFT2mx(X,NumCPU)/sqrt(size(X,1)*size(X,2)); 
return; 

내가 정적를 사용 MEX 코드를 컴파일 라이브러리 :

모든 것이 잘 작동하며 느리게 진행됩니다.

FFTW 라이브러리는 다음 인수로 컴파일하고있다 : 나는 2 쿼드 코어 AMD 옵테론 하나 개의 클러스터 노드에서이 코드를 실행하고 내가 함께 테스트

CC="gcc ${BUILD64} -fPIC" CXX="g++ ${BUILD64} -fPIC" \ 
./configure --prefix=/home/nicolas/Code/C/lib --enable-threads && 
make 
make install 

:

A = randn([2048 2048])+ i*randn([2048 2048]); 
tic, fft2mx(A,8); toc; 
tic, fftn(A); toc; 

마녀 반환 : 내 MEX 코드가

Elapsed time is 0.482021 seconds. 
Elapsed time is 0.151630 seconds. 

을 튜닝 할 수 있습니까? fftw 라이브러리의 컴파일을 최적화 할 수 있습니까? ESTIMATE 계획 수립 자만 사용하여 fftw 알고리즘의 속도를 높일 수 있습니까?

나는 통찰력을 찾고있다. 고맙습니다.


편집 : 나는 지금 한 후 일부 세그멘테이션 오류가 발생하고

# include <string.h> 
# include <stdlib.h> 
# include <stdio.h> 
# include <mex.h> 
# include <matrix.h> 
# include <math.h> 
# include </home/nicolas/Code/C/lib/include/fftw3.h> 

char *Wisfile = NULL; 
char *Wistemplate = "%s/.fftwis"; 
#define WISLEN 8 

void set_wisfile(void) 
{ 
    char *home; 
    if (Wisfile) return; 
    home = getenv("HOME"); 
    Wisfile = (char *)malloc(strlen(home) + WISLEN + 1); 
    sprintf(Wisfile, Wistemplate, home); 
} 

void cleanup(void) { 
    static fftw_plan PlanForward; 
    static int planlen; 
    static double *pr, *pi, *pr2, *pi2; 
    mexPrintf("MEX-file is terminating, destroying array\n"); 
    fftw_destroy_plan(PlanForward); 
    fftw_free(pr2); 
    fftw_free(pi2); 
    fftw_free(pr); 
    fftw_free(pi); 
} 


void mexFunction(int nlhs, mxArray *plhs[], 
       int nrhs, const mxArray *prhs[]) 
{ 

    int i, j, numCPU, NumDims; 
    const mwSize *N; 
    fftw_complex *out, *in1; 
    static double *pr, *pi, *pr2, *pi2; 
    static int planlen = 0; 
    static fftw_plan PlanForward; 
    fftw_iodim Dim[NumDims]; 
    int k, NumEl; 
    FILE *wisdom; 

    if (nrhs != 2) { 
     mexErrMsgIdAndTxt("MATLAB:FFT2mx:invalidNumInputs", 
       "Two input argument required."); 
    } 

    if (!mxIsDouble(prhs[0])) { 
     mexErrMsgIdAndTxt("MATLAB:FFT2mx:invalidNumInputs", 
       "Array must be double"); 
    } 

    numCPU = (int) mxGetScalar(prhs[1]); 
    if (numCPU > 8) { 
     mexErrMsgIdAndTxt("MATLAB:FFT2mx:invalidNumInputs", 
       "NumOfThreads < 8 requested"); 
    } 


    if (!mxIsComplex(prhs[0])) { 
     mexErrMsgIdAndTxt("MATLAB:FFT2mx:invalidNumInputs", 
       "Array must be complex"); 
    } 


    NumDims = mxGetNumberOfDimensions(prhs[0]); 
    N = mxGetDimensions(prhs[0]); 
    for(k = 0, NumEl = 1; k < NumDims; k++) 
    { 
    Dim[NumDims - k - 1].n = N[k]; 
    Dim[NumDims - k - 1].is = Dim[NumDims - k - 1].os = (k == 0) ? 1 : (N[k-1] * Dim[NumDims-k].is); 
    NumEl *= N[k]; 
    } 

/* If different size, free/destroy */ 
    if(N[0] != planlen && planlen > 0) { 
    fftw_free(pr2); 
    fftw_free(pi2); 
    fftw_free(pr); 
    fftw_free(pi); 
    fftw_destroy_plan(PlanForward); 
    planlen = 0; 
    } 
    mexAtExit(cleanup); 


/* Init */ 

fftw_init_threads(); 
// APPROACH 1 
    //pr = (double *) mxGetPr(prhs[0]); 
    //pi = (double *) mxGetPi(prhs[0]); 

// APPROACH 2 
    pr = (double *) fftw_malloc(sizeof(double) * mxGetNumberOfElements(prhs[0])); 
    pi = (double *) fftw_malloc(sizeof(double) * mxGetNumberOfElements(prhs[0])); 
    tmp1 = (double *) mxGetPr(prhs[0]); 
    tmp2 = (double *) mxGetPi(prhs[0]); 
    for(k=0;k<mxGetNumberOfElements(prhs[0]);k++) 
    { 
    pr[k] = tmp1[k]; 
    pi[k] = tmp2[k]; 
    } 

    plhs[0] = mxCreateNumericMatrix(0, 0, mxDOUBLE_CLASS, mxCOMPLEX); 
    mxSetDimensions(plhs[0], N, NumDims); 
    mxSetData(plhs[0], (double*) fftw_malloc(sizeof(double) * mxGetNumberOfElements(prhs[0]))); 
    mxSetImagData(plhs[0], (double*) fftw_malloc(sizeof(double) * mxGetNumberOfElements(prhs[0]))); 

    pr2 = mxGetPr(plhs[0]); 
    pi2 = mxGetPi(plhs[0]); 

    fftw_init_threads(); 
    fftw_plan_with_nthreads(numCPU); 

/* Get any accumulated wisdom. */ 

    set_wisfile(); 
    wisdom = fopen(Wisfile, "r"); 
    if (wisdom) { 
    fftw_import_wisdom_from_file(wisdom); 
    fclose(wisdom); 
    } 

/* Compute plan */ 

//printf("%d",planlen); 
    if(planlen == 0) { 

fftw_plan_with_nthreads(numCPU); 
    PlanForward = fftw_plan_guru_split_dft(NumDims, Dim, 0, NULL, pr, pi, pr2, pi2, FFTW_MEASURE); 
    planlen = N[0]; 
    } 

/* Save the wisdom. */ 

    wisdom = fopen(Wisfile, "w"); 
    if (wisdom) { 
    fftw_export_wisdom_to_file(wisdom); 
    fclose(wisdom); 
    } 

/* execute */ 

    fftw_execute_split_dft(PlanForward, pr, pi, pr2, pi2); 
    fftw_cleanup_threads(); 
} 

: 나는 당신이 (지혜와 정적 계획을 사용) 제안이 업데이트 된 코드를 쓴 고려

함수 호출에 여러 번 호출 (2 ~ 6 사이)하고 이유를 알 수 없습니다. 나는 포인터로 초기화하는 다른 방법을 시도했다. 필자는 어딘가에서 계획의 포인터가 해당 정적 계획을 사용하기 위해 정적이어야한다고 읽었습니다. 내가 뭘 잘못하고있는 걸 본거야?

귀하의 통찰력을 다시 한번 감사드립니다.

답변

2

문제는 각 FFT에 대한 계획을 만들고 파괴한다는 것입니다. 계획을 작성하는 것은 일반적으로 FFT 자체보다 훨씬 많은 시간이 소요됩니다. 이상적으로는 플랜을 한 번만 생성하여 파괴 한 다음 동일한 차원의 연속적인 FFT에 여러 번 다시 사용하는 것이 이상적입니다.같은 크기의 FFT에 대한 반복해서 MEX를 호출하는 경우

은 다음 memoize 계획 할 수 있습니다 (예를 들어 정적 계획 변수와 차원을 유지하고 필요한 경우에만 계획을 다시, 즉 때 치수 변경) .

다른 방법으로 계획 작성 용, 지정된 계획이있는 FFT 실행 용 및 계획 폐기 용의 세 가지 MEX 기능을 가질 수 있습니다.

위의 아키텍처 문제가 해결되면 더 나은 성능을 위해 FFTW_ESTIMATE 대신 FFTW_MEASURE을 사용해야합니다.

추가 사항 : FFTW 나비에서 SIMD 코드 생성을 사용하려면 ./configure 명령에 --enable-sse 명령을 추가 할 수 있습니다.

+0

의견을 보내 주셔서 감사합니다. 함수에 대한 단일 호출로 코드를 테스트하기 때문에 계획 생성이 패널티가되어서는 안됩니다. 맞습니까? – Nicolas

+0

계획 생성 *은 페널티입니다. 계획을 작성 (및 파기)하는 데는 시간이 걸립니다. MEX 내부의 루프에서 1000 개의 FFT를 수행하고 이것을 1000 개의 MATLAB FFT와 비교하십시오. 그러면 큰 차이가 있습니다. –

+0

여러 FFT를 적용 할 때 지혜를 저장하고 다시로드 한 후 새 계획을 만드는 것은 암기에 의한 것입니다. 나는 계획 자체를 다시 matlab에 전달한 다음 mex 함수로 다시 전달하는 방법을 모르겠다. 하지만 여전히 단일 _FFT를 적용 할 때 코드가 maltab보다 훨씬 느린 이유를 이해하지 못합니다 (이는 계획도 계산해야 함). 그런데 FFT (arround fftw_execute)를 100 번 반복하면 Matlab은 11 초, FFTW 코드는 13 초, MATLAB은 56 초, FFTW는 76 초, 500 회 반복됩니다 ... 아직 뭔가가 없습니다. – Nicolas