mpdecode_core: slice one shared subs pool per c/v-node array - #77
Conversation
init_c_v_nodes() called CALLOC() once per c_node and once per v_node for their subs arrays - potentially thousands of small, irregularly-sized allocations per decoded frame, all freed again at the end of the same run_ldpc_decoder() call. On a memory-constrained embedded target (STM32F7, ~16 KB of heap actually available under a real application's other static usage) that churn fragments the heap badly enough to fail a request well before the nominal total bytes are exhausted - confirmed on hardware: a 24-byte allocation failing with a 16200-byte allocation (DecodedBits) having just succeeded moments earlier in the same call. Every CALLOC()/MALLOC() in this file is followed by an unchecked assert(), so a failed allocation is fatal in any build without -DNDEBUG. Node degree is already fully known before any subs allocation happens, so this sums the total up front and hands out each node's subs as a slice of one shared CALLOC() instead of its own. Same total bytes, same per-node layout and population logic (untouched), just contiguous rather than thousands of separately-managed chunks - fragmentation between nodes is no longer possible. c_nodes[0].subs/v_nodes[0].subs are now the only real allocation base pointers; run_ldpc_decoder()'s cleanup is updated to match (freeing any other node's subs would corrupt the heap, since it's no longer a real malloc()/calloc() return value). Reproduced and fixed against FreeDV 700D (H_16200_9720) on an STM32F746 running the full application (USB Audio Class, HMI, OLED, etc. all resident) - RX decode now completes cleanly where it previously hung every time on the first real frame. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
@LA7LKA - Thanks. I am busy with travel this month so will take a closer look at this PR in September. What is your use case for 700D? Our main focus these days in RADE, and we don't see much use of the legacy FreeDV modes on air. |
Hi David, My use case for 700D is a QRP HF transceiver I'm building — I've already got 700D working on it standalone. Project's on GitHub if you're curious: https://github.com/LA7LKA/SDR-TRX For RADE: I've designed the radio so it shows up as a USB audio device on a PC, so it should work directly with RADE (or any other PC-side FreeDV mode) without needing anything built into the radio itself. Planning to give it a try. Thanks for taking a look at the PR when you get a chance — no rush, enjoy the travels! |
init_c_v_nodes() called CALLOC() once per c_node and once per v_node for their subs arrays - potentially thousands of small, irregularly-sized allocations per decoded frame, all freed again at the end of the same run_ldpc_decoder() call. On a memory-constrained embedded target (STM32F7, ~16 KB of heap actually available under a real application's other static usage) that churn fragments the heap badly enough to fail a request well before the nominal total bytes are exhausted - confirmed on hardware: a 24-byte allocation failing with a 16200-byte allocation (DecodedBits) having just succeeded moments earlier in the same call. Every CALLOC()/MALLOC() in this file is followed by an unchecked assert(), so a failed allocation is fatal in any build without -DNDEBUG.
Node degree is already fully known before any subs allocation happens, so this sums the total up front and hands out each node's subs as a slice of one shared CALLOC() instead of its own. Same total bytes, same per-node layout and population logic (untouched), just contiguous rather than thousands of separately-managed chunks - fragmentation between nodes is no longer possible. c_nodes[0].subs/v_nodes[0].subs are now the only real allocation base pointers; run_ldpc_decoder()'s cleanup is updated to match (freeing any other node's subs would corrupt the heap, since it's no longer a real malloc()/calloc() return value).
Reproduced and fixed against FreeDV 700D (H_16200_9720) on an STM32F746 running the full application (USB Audio Class, HMI, OLED, etc. all resident) - RX decode now completes cleanly where it previously hung every time on the first real frame.