sonos/pyFLAC

Streaming decoder is very slow

Closed this issue · 9 comments

I have been experimenting with using the encoder and decoder on some data and I have found that the stream decoder is several orders of magnitude slower than the encoder.

I have made some very minor alterations to the passthrough.py example file, starting a timer with the perf_counter_ns() command from the time module, and printing the result for both encoding and decoding. For example, encoding a file takes 0.59 ms, while decoding it seems to take over 3 seconds.

This appears to be limited to the stream decoder, where the file decoder offers performance that is on parity with the encoder. This is including the overhead that the file decoder has with performing several reads and writes to files, while the stream encoder should be much faster.

I experience this same super slow behavior when using this decoder in my project. The decoder.finish() takes about 3 seconds. I suspect this has something to do with coordination between the thread requesting the decoding and the thread that is doing the actual decoding, and not the actual decoding time required. I wish there were a way to do it all in the same thread!

If I replace the finish() with a sleep(.1) and then just look at the decoded data area, it is there in all my tests. It's like the sleep allows the decoding thread to do its work, which it can do quickly, and then we can proceed with the decoded data. But if you try to do a finish() then you are stuck waiting for multiple seconds.

If I don't have the sleep() and don't do a finish() and look immediately, then there is no decoded data in the output area.

Looking at finish() in decoder.py, I bet it is this code that is causing the 3 second delay:

    # --------------------------------------------------------------
    # Instruct the decoder to finish up and wait until it is done
    # --------------------------------------------------------------
    self._done = True
    super().finish()
    self._thread.join(timeout=3)
    if self._error:
        raise DecoderProcessException(self._error)

There is a 3 second timeout coded in there for the thread join, and it takes 3 seconds every time. Very annoying.

More information: the join() does appear to be timing out and the thread remains running! You can verify this with a check using decoder._thread.is_alive() .

So why is the join() timing out?

The problem probably only appears when doing in-memory decoding (as opposed to outputting data to a file) because the blocking on I/O helps the threads share work (compute-bound threads with GIL can be problematic), but there is still a bug somewhere because the finish() should yield to the decoder thread, but the join() always takes the full 3 second timeout and the docoder thread still never exits!

There was a bug in pyflac streaming logic (several actually) and there is a fix, which a colleague may be trying to get pulled into the repo at some point. The basic description of the problem above is correct: the thread join was never happening, the decoding thread never knew it was done.

I believe this is a duplcate of #22

I have submitted a fix #25

@kevinmcaughey @rpdrewes @brandyn Thanks for raising this issue, and apologies for the delay. I have submitted a fix for review in #26

Thank you all for reporting these issues and improving pyFLAC. The fixes should now be released in https://pypi.org/project/pyFLAC/3.0.0/

@joetoddsonos The main underlying issue, which I meant to report but hadn't gotten around to, was that the super().finish() was being called before the thread join() in finish() in decoder.py. I'm glad to see that has been fixed. Thanks!