We were working on our audio player project on mac and noticed that the power usage was so high (about 7x that of google chrome doing the same workload.)
I used xcode's energy profiling tool, one of the problems was we had too much cpu-wake overhead.
According to xcode:
Each time the CPU wakes from idle, there is an incurred energy penalty. If the wakes are high, and the CPU utilization per wake is low, then you should consider batching work.
We had narrowed down the problem to a usleep function call.
In our code, the audio decoder is a producer that produces audio data and inserts them into the consumer -- the audio player. Our audio player is base on OpenAL, which has a buffer for the audio data.
Because the audio player can be slower than the producer, we always check the buffer availability before giving a new audio data to the audio player. If no buffer is available, we usleep for a while and try again. So the code looks like:
void playAudioBuffer(Data *data)
{
while(no buffer is available)
{
usleep()
}
process data.
}
Knowing that usleep is a problem, the first thing we did was simply removing usleep(). (Because OpenAL doesn't seem to provide callback or any other way, polling seems to be the only option.) We successfully reduced the power usage by half after doing this.
Then, yesterday, we tried
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;
}
}
This is an experiment we tried by accident. It doesn't really make sense to us as logically it performs the same wait. And the use of the conditional variable isn't correct, because the variable "available" is only accessed by one thread. But it actually reduced our energy consumption by 90%, the cpu usage of the thread dropped a lot. Now we are better than chrome. But How is conditional variable implemented differently than the following code? Why does it save us power?
mutex lock;
while(condition is false)
{
mutex unlock;
usleep();
mutex lock;
}
...
mutex unlock
...
(We use mac's activity monitor (energy number) and cpu usage profiling tool to measure the energy consumption.)
Condition variables are used to wait until a particular condition predicate becomes true. This condition predicate is set by another thread, usually the one that signals the condition.
Condition variables are synchronization primitives that enable threads to wait until a particular condition occurs. Condition variables are user-mode objects that cannot be shared across processes. Condition variables enable threads to atomically release a lock and enter the sleeping state.
Viewed in isolation, a condition variable allows threads to block and to be woken by other threads. However, condition variables are designed to be used in a specific way; a condition variable interacts with a mutex to make it easy to wait for an arbitrary condition on state protected by the mutex.
It allows threads to wait until particular condition occurs. It is generally used to solve problem of some critical sections in process synchronization. It is generally used with mutex to signal changing states from one thread to another one.
I may be wrong, but as far as I understand when you use conditional variable to implement waiting for buffer data income. The main thing it does, it puts thread, which renders this condition variable, to sleep until a signal associated with it wakes up this thread. That is why you get less wake up overhead and use resources more efficiently.
Here are a links to working with threads in Linux, where I read about it:
May be this will give you some understanding why and how it happens.
Again I'm not entirely sure that I'm totally right, but it seems to me like a right direction.
Sorry for my pure English.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With