下面的代码演示了我要尝试执行的操作,它与我的原始代码(此处未包含)存在相同的问题。我有频谱图代码,并且试图通过使用多个线程(我的计算机具有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)。
虽然现在看起来好多了,但仍远未达到最佳状态。
答案 0 :(得分:2)
写入vWndFFT
的锁会受到伤害,对分配给new
和pReal
的{{1}}的重复(泄漏)调用也会受到伤害(这些应该在for之外)循环)。
但是真正的性能杀手可能是您的循环等待线程完成:pImg
。这将以一种非常不友好的方式消耗一个可用线程。
一种快速的解决方法(不建议使用)是添加一个while(TWorkerThread::GetNumThreads()>0);
(或2、5或10),以使循环不连续。
一个更好的解决方案是让主线程成为您的计算线程之一,并为该线程提供一种方法(一旦完成所有处理),就可以简单地等待另一个线程完成而不消耗内核,使用Windows上的sleep(1)
之类的东西。
尝试线程代码的一种简单方法是简单地运行线程,但仅使用一个线程。性能应与非线程版本大致相同,并且结果应匹配。