Hi,

Sorry, I sent my previous mail with a placeholder left in it (RESULT_OF_TEST_SUITE_UNDER_SANITIZERS). Here is what should have been there.

The test suite (ctest) also passes on the series built with AddressSanitizer, including the streams test and its corruption handling part, with no report.

With UBSan, the streams test stops at the first corrupted file on a signed integer overflow in the prediction. Master does exactly the same on that file, in FLAC__fixed_restore_signal() (6 * 364632807), since the corrupted data gives arbitrary samples. The series keeps the same no_sanitize attribute as the existing restore functions, only for fuzzing builds. In normal builds both decode that file to the same output.

Thanks !

Le 29/10/2026 à 15:34, olivier tristan a écrit :
Hi,

Thanks for the pointer, I hadn't run it. I have now.

I decoded all 86 files of the testbench (subset, uncommon and faulty) with flac built from master and from the series, in three ways: flac -t, flac -d to raw, and flac -d -F.

For every file and every mode the two give the same decoded output, the same exit code and the same messages.

I also ran the series built with AddressSanitizer and UBSan on the same files: no report, and the same results as above. RESULT_OF_TEST_SUITE_UNDER_SANITIZERS

No hurry on the review, I understand. If it helps, I can open the patches as separate pull requests, starting with the four small ones (rice parameter 0, the shift, the side subframe test and the range check), which are easy to check on their own.

Thanks !

Le 28/10/2026 à 20:53, Martijn van Beurden a écrit :
Hi,

Thanks for working on this. I've tried to speed up decoding in the
past, and didn't get any improvement at all really.

I currently have very little time (if any) to review anything, as
might be apparent from the recent PRs on GitHub. So, it might be a
while before this is merged. Anyway, have you used the FLAC decoding
testbench to verify decoding of edge cases? See
https://xiph.org/flac/2022/09/08/decoder-testbench.html

Kind regards, Martijn van Beurden

Op do 22 okt 2026 om 16:22 schreef olivier tristan <[email protected]>:
Hi all,

I work at UVI, where we make virtual instruments and sample libraries.
Our libraries ship their samples as FLAC, and the sampler streams them
from disk while you play.

Every note that is not already in the cache starts a stream that decodes
FLAC frames on a worker thread. With a lot of voices, decoding is a
noticeable part of the CPU load, so I spent some time profiling libFLAC
on our material.

The result is a series of 7 patches against current master:

https://github.com/UVI-Dev/flac-fork/commits/decode-speedups

Decoding is 36 to 44% faster on Apple M1 and 21 to 43% faster on x86-64,
depending on compiler and material, with bit-identical output. I
measured every commit on its own on both platforms and only kept changes
that are not slower anywhere. Each commit message has its numbers.

In short:

1. Rice parameter 0 partitions go through the fast block reader instead
of one read_unary call per residual. Helps quiet passages (decay tails,
which our samples have plenty of), no change on dense music.
2. The stop bit and the LSBs are dropped from the bit buffer with one
shift instead of two. The buffer goes from one residual to the next
through that chain, so one operation less there shows.
3. undo_channel_coding() tests side_subframe_in_use once per frame
instead of once per sample, so the side-coded loops vectorize with MSVC
and clang-cl (clang on macOS already managed).
4. The "decoded sample fits bps" check no longer exits early, so it
vectorizes too.
5. LPC and fixed subframes are restored while the residual is read,
instead of decoding the residual first and running lpc_restore_signal()
in a second pass.
6. Frame CRC-16 with PMULL folding on arm64.
7. Frame CRC-16 with PCLMULQDQ on x86-64, chosen at run time.

Patch 5 is where most of the gain is. The rice decoding is bound by the
bit position, the restore by the previous samples, and in one loop the
two chains overlap. We got the idea from dr_flac, which does this.
Residuals are still stored for the client.

For orders up to 12 the loop is instantiated per order, and the
prediction of the next sample but its newest tap is computed while the
current sample is restored. That way the sample to sample dependency is
one multiply-add, the shift and the add, whatever the compiler does with
the additions.

The newest sample is kept in a variable, but the 11 older ones only
where there are enough registers. On x86 a window of 12 does not fit,
both MSVC and clang spill it and shift it in memory for every sample, so
there the older samples are read back from the output buffer. On arm64
reading them back costs up to 5%.

So bitreader.c picks the window with FLAC__CPU_X86_64 / FLAC__CPU_IA32:
x86 reads the older samples back from the buffer, other architectures
keep all 12 in variables. It comes down to the number of registers, and
each choice measured best on its own side.

About 6 and 7: the table CRC-16 was 10 to 20% of the decoding time. We
first considered an option to skip it for frames that follow a valid
one, but computing it faster keeps every frame checked and now costs
almost nothing, so there is no such option in this series.

Numbers (ns per sample, all channels, full decode from memory, best of 8
runs, CRC checked):

                              master   series   change
    Apple M1, clang 15
      16-bit samples            5.65     3.18   -43.7%
      24-bit samples            6.86     3.91   -43.1%
      16-bit music              7.01     4.47   -36.2%

    Core Ultra 9 185H, MSVC 19.29
      16-bit samples            6.69     4.58   -31.6%
      24-bit samples            9.39     5.49   -41.6%
      16-bit music              7.83     5.75   -26.5%

    Core Ultra 9 185H, clang-cl 23
      16-bit samples            5.76     4.02   -30.2%
      24-bit samples            7.08     5.12   -27.7%
      16-bit music              6.52     5.18   -20.6%

    Core Ultra 9 185H, MSVC 19.51 (VS 2026)
      16-bit samples            7.66     4.45   -41.8%
      24-bit samples            7.91     5.58   -29.4%
      16-bit music              9.57     5.42   -43.4%

With MSVC 19.51, master itself is slower than with 19.29 on 16-bit
samples and music (and faster on 24-bit), while the series runs at the
same speed with both. Better compare the absolute numbers there.

Testing: the whole test suite passes (ctest, including the streams test)
on macOS arm64, and the decoded output is identical to master for every
commit on all the sets above. flac -t passes on all the test files on
Windows. The PCLMULQDQ CRC was also checked against a bitwise CRC-16 on
random data.

Every commit builds on its own without new warnings with clang -Wall
-Wextra, and the final state has no new warning with MSVC 19.29 (default
and /W4) and 19.51.

Things I would like your opinion on:

- bitreader.c now knows about LPC prediction (it includes lpc.h), and
the per-order instantiation leans on macros around the deduplicated rice
reader. I tried to keep it close to how the BMI2 variant is done, but
there may be a layout you prefer.
- I have only tested arm64 macOS and x86-64 Windows. Linux/GCC results
would be welcome.

I can split this into separate pull requests if that is easier to
review, starting with 1 to 4 which are small.

Thanks,

--
Olivier Tristan
Research & Development
www.uvi.net

_______________________________________________
flac-dev mailing list -- [email protected]
To unsubscribe send an email to [email protected]

--
Olivier Tristan
Research & Development
www.uvi.net

_______________________________________________
flac-dev mailing list -- [email protected]
To unsubscribe send an email to [email protected]

Reply via email to