C++for循环太慢

C++ for loop too slow

本文关键字:循环 C++for      更新时间:2023-10-16

我正在尝试用PortAudio制作一个音频应用程序。我的回调函数一直很慢,它一直在创建持续的欠载。我一个接一个地删除了回调中的所有内容,直到发现问题:for循环。我删除了所有内容,所以回调函数中唯一发生的事情就是for循环,它仍然会导致不足运行。我知道这是for循环,因为当我减少迭代次数时,欠载就会消失。

static int patestCallback(const void *inputBuffer, void *outputBuffer,
unsigned long framesPerBuffer,
const PaStreamCallbackTimeInfo *timeInfo,
PaStreamCallbackFlags statusFlags, void *userData)
{
int x = 0;
for (int jj = 0; jj < 10000; jj++)
{
x++; // for testing, not actually used
};
return paContinue;
}

以下是我的完整测试代码:https://gist.github.com/johnroper100/b87641f5609dbb49bc3c1121b1f4daf1

对于这个问题来说,这并不是真正必要的,但我在python等价物(sounddevice)中进行了相同的回调,没有问题。

来自API文档:

当流正在运行时,PortAudio会定期调用流回调。回调函数负责处理通过输入和输出参数传递的音频样本的缓冲区。

PortAudio流回调以非常高的优先级或实时优先级运行。它被要求始终如一地在最后期限前完成任务。不要从流回调中分配内存、访问文件系统、调用库函数或调用其他函数,这些函数可能会阻塞或需要不可预测的时间才能完成。

为了使流保持无故障操作,回调必须以比录制和/或播放更快的速度消耗和返回音频数据PortAudio预计每个回调调用的执行时间可能接近流采样率下frameCount音频帧的持续时间。期望能够在PortAudio回调中使用70%或更多的可用CPU时间是合理的。然而,由于缓冲区大小自适应和其他因素,并非所有主机API都能够在任意固定回调缓冲区大小的重CPU负载下保证音频稳定性当需要高回调CPU利用率时,可以通过使用paFramesPerBufferUnspecified作为Pa_OpenStream()framesPerBuffer参数来实现最稳健的行为

我突出显示了相关部分。

一个没有任何内容的for循环对CPU来说仍然是一项艰巨的工作。

如果你不想让你的CPU把所有的功率都花在for循环上,你至少应该让for循环包含一些放弃CPU周期的东西。

最简单的方法是在其中调用sleep()或nanosleep(。

更高级的方法是使用具有适当机制的线程,让它们等待所需的操作。

我相信文档解释了回调函数无法正常工作的原因(在提供的答案@ÁdámHunyadi中)。但我不确定它是否解释了为什么for循环太慢。

为了使流保持无故障操作,回调必须以比录制更快的速度消耗和返回音频数据,和/或播放PortAudio预计每次回调调用可能执行的持续时间接近frameCount音频的持续时间帧。期望能够利用PortAudio中70%或更多的可用CPU时间回调。然而,由于缓冲区大小自适应和其他因素所有主机API都能够保证在繁重的CPU下音频的稳定性使用任意固定的回调缓冲区大小加载高回调时CPU利用率是必需的—可以实现最稳健的行为通过使用paFramesPerBufferUnspecified作为Pa_OpenStream()framesPerBuffer参数

查看PortAudio文档示例中的示例,它们通过framesPerBuffer迭代中的for-loop进行迭代(在文档中定义为framesCount)接近frameCount的持续时间。但在您的代码示例中,您也在做同样的事情,但您做的是vector的大小(我认为这不是接近frameCount的持续时间)。仅使用一个for-loop而不是两个,并使用framesPerBuffer而不是10000作为最大迭代次数,可能会解决"循环太慢">问题。

除了可能导致循环或整个程序速度减慢的API特定问题外,我将对您的一般问题给出一般建议。如果忽略返回值,请始终支持前缀(++i;)而不是后缀(i++;)增量,因为前缀不会返回您正在应用运算符的值/对象的副本。因此,可以避免不必要的复制操作。尽管大多数现代编译器确实会在运行中为您替换它,但它们可能会以某种方式被您的代码欺骗,因此最好养成使用前缀增量的习惯。当然,为了获得最高性能,您需要使用-O3参数编译代码,并确保编译器能够做到最好。进一步优化循环时需要考虑的另一个选项是多线程。

捕获新帧的代码可能是在patestCallback结束后调用的,如果你不能在截止日期前完成任务,这意味着你的CPU不够强大(=处理太复杂)。这不是一个解释(为什么),而是一个解决方案,如果你的CPU中有多个Core(有时即使你有一个Core也能工作),你可以创建一个处理音频的线程,回调应该只复制(或更改指针)到要处理的音频,这使你的主线程(音频捕获线程)不会等待太久。