我们在 Mac 上开发音频播放器项目,发现耗电量非常高(大约是 google chrome 执行相同工作负载的 7 倍。)
我使用了 xcode 的能量分析工具,其中一个问题是我们有太多的 cpu-wake 开销。
根据xcode:
每次 CPU 从空闲状态唤醒时,都会产生能量损失。如果唤醒次数很高,并且每次唤醒的 CPU 利用率很低,那么您应该考虑批处理工作。
我们已将问题缩小到一个 usleep 函数调用。
在我们的代码中,音频解码器是一个生产者,它产生音频数据并将它们插入消费者——音频播放器。我们的音频播放器基于 OpenAL,它有一个音频数据缓冲区。
因为音频播放器可能比生产者慢,所以我们总是在向音频播放器提供新的音频数据之前检查缓冲区的可用性。如果没有可用的缓冲区,我们会睡一会儿,然后再试一次。所以代码看起来像:
void playAudioBuffer(Data *data)
{
while(no buffer is available)
{
usleep()
}
process data.
}
知道 usleep 是个问题,我们做的第一件事就是简单地删除 usleep()。(因为 OpenAL 似乎没有提供回调或任何其他方式,所以轮询似乎是唯一的选择。)在这样做之后,我们成功地将功耗减少了一半。
然后,昨天,我们尝试了
for(int i =0; i<attempts; ++i)
{
std::unique_lock<std::mutex> lk(m);
cv.wait_for(lk, 3, []{
available = checkBufferAvailable();
return available;
})
if (available)
{
process buf;
}
}
这是我们偶然尝试的一个实验。这对我们来说真的没有意义,因为从逻辑上讲它执行相同的等待。而且条件变量的使用是不正确的,因为变量“available”只能被一个线程访问。但它实际上将我们的能耗降低了 90%,线程的 cpu 使用率下降了很多。现在我们比铬更好。但是条件变量的实现方式与以下代码有何不同?为什么它可以节省我们的电力?
mutex lock;
while(condition is false)
{
mutex unlock;
usleep();
mutex lock;
}
...
mutex unlock
...
(我们使用 mac 的活动监视器(能量数)和 cpu 使用分析工具来测量能量消耗。)