设计一个可扩展的多线程应用程序

时间:2019-01-06 18:44:03

标签: c++ multithreading optimization vcl

下面的代码演示了我要尝试执行的操作,它与我的原始代码(此处未包含)存在相同的问题。我有频谱图代码,并且试图通过使用多个线程(我的计算机具有4个核心)来提高其性能。频谱图代码基本上在许多重叠的帧上计算FFT(这些帧对应于特定时间的声音样本)。

作为一个例子,我们有1000帧重叠50%。 如果我们使用4个线程,则每个线程应处理250个帧。重叠的帧只是意味着如果我们的帧长度为1024个样本,则第一个 帧的范围是0-1023,第二帧是512-1535,第三帧是1024-2047,等等(重叠512个样本)。

创建和使用线程的代码

void __fastcall TForm1::Button1Click(TObject *Sender)
{
    numThreads = 4;
    fftLen = 1024;
    numWindows = 10000;
    int startTime = GetTickCount();

    numOverlappingWindows = numWindows*2;
    overlap = fftLen/2;
    const unsigned numElem = fftLen*numWindows+overlap;

    rx = new float[numElem];
    for(int i=0; i<numElem; i++) {
        rx[i] = rand();
    }
    useThreads = true;
    vWThread.reserve(numOverlappingWindows);

    if(useThreads){
    for(int i=0;i<numThreads;i++){
            TWorkerThread *pWorkerThread = new TWorkerThread(true); 
            pWorkerThread->SetWorkerMethodCallback(&CalculateWindowFFTs);//this is called in TWorkerThread::Execute
            vWThread.push_back(pWorkerThread);
        }
        pLock = new TCriticalSection();

        for(int i=0;i<numThreads;i++){ //start the threads
            vWThread.at(i)->Resume();
        }

        while(TWorkerThread::GetNumThreads()>0);
        }else CalculateWindowFFTs();

        int endTime = GetTickCount();

        Label1->Caption = IntToStr(endTime-startTime);
}
void TForm1::CalculateWindowFFTs(){

        unsigned startWnd = 0, endWnd = numOverlappingWindows, threadId;

        if(useThreads){
            threadId = TWorkerThread::GetCurrentThreadId();
            unsigned wndPerThread = numOverlappingWindows/numThreads;
            startWnd = (threadId-1)*wndPerThread;
            endWnd   =  threadId*wndPerThread;

        if(numThreads==threadId){
            endWnd = numOverlappingWindows;
            }
        }

    float *pReal, *pImg;

    for(unsigned i=startWnd; i<endWnd; i++){

            pReal = new float[fftLen];
            pImg  = new float[fftLen];

            memcpy(pReal, &rx[i*overlap], fftLen*sizeof(float));
            memset(pImg, '0', fftLen);
            FFT(pReal, pImg, fftLen);  //perform an in place FFT

            pLock->Acquire();
            vWndFFT.push_back(pReal);
            vWndFFT.push_back(pImg);
            pLock->Release();
    }
}

void TForm1::FFT(float *rx, float *ix, int fftSize)
{
    int i, j, k, m;
    float rxt, ixt;

    m = log(fftSize)/log(2);
    int fftSizeHalf = fftSize/2;
    j = k = fftSizeHalf;

        for (i = 1; i < (fftSize-1); i++){
            if (i < j) {

            rxt = rx[j];
            ixt = ix[j];
            rx[j] = rx[i];
            ix[j] = ix[i];
            rx[i] = rxt;
            ix[i] = ixt;
            }
            k = fftSizeHalf;

            while (k <= j){
                j = j - k;
                k = k/2;
                }
            j = j + k;

        }    //end for
    int le, le2, l, ip;
    float sr, si, ur, ui;
    for (k = 1; k <= m; k++) {
        le = pow(2, k);
        le2 = le/2;
        ur = 1;
        ui = 0;
        sr = cos(PI/le2);
        si = -sin(PI/le2);
        for (j = 1; j <= le2; j++) {
            l = j - 1;
            for (i = l; i < fftSize; i += le) {
                ip = i + le2;
                rxt = rx[ip] * ur - ix[ip] * ui;
                ixt = rx[ip] * ui + ix[ip] * ur;
                rx[ip] = rx[i] - rxt;
                ix[ip] = ix[i] - ixt;
                rx[i] = rx[i] + rxt;
                ix[i] = ix[i] + ixt;
            }    //end for
            rxt = ur;
            ur = rxt * sr - ui * si;
            ui = rxt * si + ui * sr;
        }
    }
}

虽然很容易将此进程划分为多个线程,但是与单线程版本(<10%)相比,性能仅得到了一点改善。 有趣的是,如果我将线程数增加到100个,则速度确实提高了约25%,这令人惊讶,因为 在这种情况下,我希望线程上下文切换开销是一个因素。

起初,我认为性能不佳的主要原因是无法写入向量对象,因此我尝试了一系列向量( 向量(每个线程的向量),从而消除了对锁的需求,但性能几乎保持不变。

pVfft = new vector<float*>[numThreads];//create an array of vectors

  //and then in CalculateWindowFFTs, do something like

    vector<float*> &vThr = pVfft[threadId-1];
    for(unsigned i=startWnd; i<endWnd; i++){

            pReal = new float[fftLen];
            pImg  = new float[fftLen];

            memcpy(pReal, &rx[i*overlap], fftLen*sizeof(float));
            memset(pImg, '0', fftLen);
            FFT(pReal, pImg, fftLen);  //perform an in place FFT

            vThr.push_back(pReal);      
    }

我想我在这里遇到了缓存问题,尽管我不确定如何更改设计以得到一个可扩展的解决方案。

如果您认为很重要,我还可以提供TWorkerThread的代码。

非常感谢您的帮助。

谢谢

更新:

根据1201ProgramAlarm的建议,我删除了while循环,并在系统上将速度提高了15-20%。现在我的主线程没有主动等待线程完成,而是在所有工作线程都完成之后(例如,TWorkerThread完成后,我通过TThread::Synchronize在主线程上执行了代码numThreads达到0)。

虽然现在看起来好多了,但仍远未达到最佳状态。

1 个答案:

答案 0 :(得分:2)

写入vWndFFT的锁会受到伤害,对分配给newpReal的{​​{1}}的重复(泄漏)调用也会受到伤害(这些应该在for之外)循环)。

但是真正的性能杀手可能是您的循环等待线程完成:pImg。这将以一种非常不友好的方式消耗一个可用线程。

一种快速的解决方法(不建议使用)是添加一个while(TWorkerThread::GetNumThreads()>0);(或2、5或10),以使循环不连续。

一个更好的解决方案是让主线程成为您的计算线程之一,并为该线程提供一种方法(一旦完成所有处理),就可以简单地等待另一个线程完成而不消耗内核,使用Windows上的sleep(1)之类的东西。

尝试线程代码的一种简单方法是简单地运行线程,但仅使用一个线程。性能应与非线程版本大致相同,并且结果应匹配。