【问题标题】:Designing a multithreaded application that scales well设计一个可扩展的多线程应用程序
【发布时间】:2019-01-06 18:44:03
【问题描述】:

下面的代码是我正在尝试做的演示,它与我的原始代码存在相同的问题(此处未包含)。我有频谱图代码,我正在尝试通过使用多个线程来提高其性能(我的计算机有 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;
        }
    }
}

虽然很容易将此进程划分为多个线程,但与单线程版本相比,性能仅略有提高 (

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

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% 的速度。现在我的主线程并没有主动等待线程完成,而是在所有工作线程完成后(即当numThreads 达到 0 时),我通过 TThread::Synchronize 在主线程上执行代码 TWorkerThread

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

【问题讨论】:

  • 好吧,与其猜测,我建议使用一个好的 C++ 分析器。有很多
  • 您是在分析发布还是调试版本?
  • 这是一个调试版本。
  • 有人可以帮我解决这个问题吗?我知道这不是一个简单的问题,因为人们不能只用谷歌搜索答案。

标签: c++ multithreading optimization vcl


【解决方案1】:

写入vWndFFT 的锁会受到影响,分配给pRealpImg 的对new 的重复(泄漏)调用也会受到影响(这些应该在for 循环之外)。

但真正的性能杀手可能是您等待线程完成的循环:while(TWorkerThread::GetNumThreads()&gt;0);。这将以一种非常不友好的方式消耗一个可用线程。

一种快速解决方法(不推荐)是添加一个sleep(1)(或 2、5 或 10)以使循环不连续。

更好的解决方案是让主线程成为您的计算线程之一,并让该线程(一旦完成所有处理)只需等待另一个线程完成而不消耗核心,使用类似 WaitForMultipleObjects 在 Windows 上可用的东西。

尝试线程代码的一种简单方法是简单地运行线程,但只使用一个线程。性能应该和非线程版本差不多,结果应该匹配。

【讨论】:

  • 我已经尝试在线程版本中仅使用 1 个线程,并且它的性能与非线程版本大致相同,因此没有任何意外。正如我在帖子中提到的那样,我也可以摆脱锁,但这也无济于事。
  • 感谢您的回答。在 for 循环中没有对new 的泄漏调用,并且这些调用必须在循环内,因为它们代表后续重叠的 FFT 帧。我将指针存储在vWndFFT 中,以便以后不再需要时释放它们。至于while (TWorkerThread::GetNumThreads()&gt;0);,这个调用是在主线程的上下文中执行的,但我知道它不必要地消耗了大量的处理时间。您将主线程作为计算线程之一的建议似乎很好,尽管我真的必须尝试一下才能确定。
  • 我想我可以完全取消这个 while 循环。由于我正在递减numThreads,所以当numThreads 达到0 时,我可以调用TThread::Synchronize(&amp;NotifyFinished)。回调NotifyFinished 在主线程上执行。不确定这是否会提高性能。
猜你喜欢
  • 2011-10-03
  • 2015-05-25
  • 2010-10-28
  • 1970-01-01
  • 1970-01-01
  • 2021-06-29
  • 2010-09-05
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多