Re: Accumulating into output_items[0] across multiple general_work()
Wael Farah <[email protected]> Fri, 5 Jun 2026 12:13:13 -0700
| Newsgroups | gmane.comp.gnu.radio.general |
|---|---|
| Message-ID | <[email protected]> |
Hi Dani, Thanks for your response, very informative! I guess it's probably reasonable not to base a block implementation on = an assumption of =E2=80=9Cit works because I looked at the source = code=E2=80=9D. And you=E2=80=99re right, it=E2=80=99s true that exotic buffer = implementations are free to hand off output buffers how ever they want = to (which I think need very specific reasons to do so, but still not = contract-breaking).=20 My block is actually not (just) an FFT, it=E2=80=99s a bit more = complicated, I just mentioned that for the sake of giving a simple = example use case, but your insight is valuable. Thanks again, Wael > On Jun 4, 2026, at 11:43=E2=80=AFPM, Daniel Est=C3=A9vez = <[email protected]> wrote: >=20 > Hi Wael, >=20 > This is a great question. My understanding is basically the same as = the conclusion you've arrived to. In practice this works correctly for = GR3 regular CPU buffers, because the buffer is a ring buffer and the = write pointer is not advanced if you return zero as the number of items = that your general_work() call has produced. So the next general_work() = call sees the same data that the previous call wrote in the output = buffer. If I remember correctly, this also works in the same way for GR4 = regular CPU buffers. >=20 > However, I think this is going into undocumented assumptions of how = the buffer system should work. I don't think this is solidly written = down anywhere, but for me the contract that general_work() has with the = output buffer is that it should never read data from the output buffer, = and it needs to write exactly as many items at the beginning of the = buffer as the number of items that are being produced. Anything else = might break under an exotic customs buffer implementation. For instance, = I could imagine an implementation which hands off an output buffer taken = from a common buffer pool, with no guarantee that the buffer is the same = in consecutive calls even if no data was produced. >=20 > Since your use case is accumulating many FFT frames, I would say that = storing the accumulator as a member in the block class and copying the = result to the output buffer whenever the accumulation has finished is = quite acceptable. The output data rate is going to be quite low, so the = memory copies add very little overhead. >=20 > Another comment is that a very long integration can be realized by = cascading multiple shorter integrations, which can be realized with the = in-tree Integrate block. For instance you could integrate 1e9 FFTs by = cascading three Integrate blocks, each set to a integration of 1e3. When = using floating point numbers this approach is also better numerically, = because otherwise you are sequentially adding numbers to an accumulator = that ends up being approximately 1e9 times larger than the input = numbers, which doesn't really work because the float32 machine epsilon = is 1e-7. >=20 > Best, > Dani. >=20 > On 05/06/2026 03:27, Wael Farah wrote: >> Hi all, >> I have a block whose output items are running averages over a long = integration (for the sake of simplicity say a power spectrum accumulated = over millions of FFT frames, way too many to hold as one input buffer). >> The implementation that fell out naturally is: in general_work(), add = the next batch of partial contributions directly into output_items[0] = [n_emitted], return 0 while the integration is still incomplete, and = only return n_emitted > 0 when one or more integrations are done. It's = pretty tempting to do as this would practically avoid an extra memory = allocation for an internal buffer and a memcpy back to output_items when = accumulation is ready. >> However, this relies on the assumption that returning 0 leaves the = write pointer unadvanced, so the next general_work() call sees the same = memory at output_items[0][n_emitted] and I can keep adding into it. >> As far as I can tell, this works on current GR (no-op when = produce_each(0) <https://github.com/gnuradio/gnuradio/blob/main/ = gnuradio-runtime/lib/block_detail.cc#L123-L132> -> write pointer not = updated), but it=E2=80=99s more like an implementation detail rather = than it being documented as part of the scheduler API. >> Two questions: >> 1) Is the "accumulate into output_items[0] across calls" pattern = supported, or am I in undocumented/unidentified scheduler/buffer = behavior territory? >> 2) If it's not supported, is there any reason beyond "no API = guarantee=E2=80=9D; e.g. would it break under certain custom buffers or = futuristically GR4? >> If the answer is just "use an internal buffer," happy to refactor. >> Thanks! >> Wael >=20