【问题标题】:C++ for loop too slowC++ for 循环太慢
【发布时间】:2020-09-09 12:45:17
【问题描述】:

我正在尝试使用 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)中做了相同的回调,没有问题。

【问题讨论】:

  • 如果完全删除for 循环会发生什么?
  • 如果优化,编译器将完全删除循环。我不确定您当前的代码在循环中没有任何内容。
  • 我的水晶球说您正在测试未优化的调试版本(大多数编译器的默认设置)而不是优化的发布版本...
  • sn-p 代码只是为了演示还是你已经实际运行过?询问是因为返回值缺少声明并且代码不应该编译。
  • 如果您提供问题的minimal reproducible example 的完整源代码,可能会有所帮助。您在 StackOverflow 上发布的代码不可重现,您在 GibHub 上发布的代码也不是最少的,因为它对音频数据进行了广泛的操作(这似乎没有必要,并且可能包含错误)。为了一个最小的可重现示例,您可能需要考虑通过算法生成音频数据,例如通过生成完全静音的音频数据。

标签: c++ callback portaudio


【解决方案1】:

From the API documentation:

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

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

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

我突出显示了相关部分。

【讨论】:

  • 阅读文档后我同意这个答案,但我不确定这是否是所问的。
  • @darclader:OP 只提供了不完整的信息,也没有回应几个要求澄清的 cmets。因此,有必要让答案猜测问题可能是什么。我认为这个答案做得很好。
  • @AndreasWenzel 所以如果 OP 没有做出足够的解释,就可以猜测 stackoverflow 上的答案?
  • @darclader:通常情况下,此类问题将被关闭。但是,当问题悬赏开放时,无法将其标记为关闭。
【解决方案2】:

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

如果你不希望你的 CPU 把所有的精力都花在那个 for 循环上,你至少应该让那个 for 循环包含一些放弃 CPU 周期的东西。

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

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

【讨论】:

  • OP 发布的函数是PortAudio callback function,它旨在在每次可用或需要新数据时调用。根据该函数的文档,它很可能已经被另一个线程调用,该线程由 PortAudio 内部处理。此外,根据该文档,您不应调用任何导致上下文切换的函数,例如sleep
  • @AndreasWenzel 因此,应该在另一个线程中进行这些繁重的计算,并在此回调中读取计算数据。另一个线程应该没有这样的限制(不调用任何导致上下文切换的函数)。
  • @Oliort:这需要回调函数的线程和工作线程之间的线程同步。根据我上面链接中的 PortAudio 文档,不建议这样做。
  • @AndreasWenzel,也许应该使用非锁定线程同步原语(具有比较交换机制的原子变量)。这也违反了“不要使用任何可能依赖于操作系统的东西”,但在我看来,在这种情况下,这种违反是最不可能的。原子也不包含在“不使用”列表中。
【解决方案3】:

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

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

查看PortAudio Doc Example 中的示例,他们遍历for-loopframesPerBuffer 迭代(在文档中定义为framesCount)。 接近 frameCount 的持续时间。但是在您的代码示例中,您正在做同样的事情 BUT 你做的事情是 vector times 的大小(我相信这不是 接近帧数)。仅使用一个for-loop 而不是两个,并使用framesPerBuffer 而不是10000 作为最大迭代次数可能会解决“for-loop 太慢”的问题。

【讨论】:

    【解决方案4】:

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

    【讨论】:

      【解决方案5】:

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

      【讨论】:

        猜你喜欢
        • 2016-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-05-17
        • 2018-12-18
        • 2020-08-04
        • 2012-12-24
        • 1970-01-01
        相关资源
        最近更新 更多