Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Semantic of the Sync and Async Ports #369

Open
drslebedev opened this issue Jun 24, 2024 · 6 comments
Open

Semantic of the Sync and Async Ports #369

drslebedev opened this issue Jun 24, 2024 · 6 comments

Comments

@drslebedev
Copy link
Contributor

drslebedev commented Jun 24, 2024

With this issue we want to review the semantic of Async ports and clearly define the difference between Sync and Async cases.

Number of samples constraints

There are two types of constraints used to calculate the input and output number of samples to be processed:

  1. Port Constraints: Min/max number of samples set individually for each port.
  2. Resampling Constraints: Numerator, denominator, and stride set for each block.
  • Port constraints apply to both Sync and Async ports.
  • Resampling constraints apply only to Sync ports.

Sync and Async Port Behaviors

Sync Ports:

  • Same number of samples for all Sync ports.
  • Fixed M:N ratio between input and output samples. The processBulk is called with k*M inputs and k*N outputs.
  • Both port constraints and resampling constraints are applied.

Async Ports:

  • The processBulk function is called independently of the available samples on other Sync and/or Async ports.
  • Only port constraints are applied; the input/output sample ratio is undefined.
  • The user can change the sample ratio iteration-to-iteration using input.consume() and output.publish().

Examples

  1. 4 Sync + 1 Async Inputs -> 1 Sync Output:

    • All 4 Sync inputs have consistent input (same number of samples) and Async has some input samples (fulfilling port constraints) -> call processBulk.
    • All 4 Sync inputs have consistent input (fulfilling port + resampling constraints), Async has no input (or input does not fulfill port constraints) -> call processBulk (Async port has no input samples).
    • Samples do not fulfill Sync input constraints, Async has input (fulfilling port constraints) -> call processBulk (no samples for input Sync ports, no samples for output Sync port, only samples for input Async port).
    • No Sync input available and no Async data -> do not call processBulk.
  2. Sync and/or Async Inputs -> 4 Sync + 1 Async Outputs:

    • If at least one Async output port is present, always call processBulk (unless Async output port constraints are not met). This is because the user can call output.publish() even if no input samples are present. Note: This can lead to performance issues due to busy polling. processBulk can be invoked upto 4G/s.
    • Consider introducing a policy to prevent this and only call processBulk for Async ports if input samples are available. This can be a port policy.

consume() / publish() Only for Async Ports

  • Users cannot override the M:N resampling ratio and stride for Sync ports using consume()/publish(). This ensures the resampling ratio and the consistent number of input/output samples are maintained.
  • Can be enforced in ~ConsumablePortRange and its counterpart or even statically disabled.
  • Disable produce/consume or use static_assert for better error messages.
  • Users who need this functionality must use Async ports.

TODO

  • Update the documentation for Async/Sync structs.
  • Check/update the Block implementation, ConsumablePortRange logic, and ProducablePortRange.
@daniestevez
Copy link
Contributor

daniestevez commented Jun 28, 2024

The processBulk function is called independently of the available samples on other Sync and/or Async ports.

Can you clarify when processBulk is called and when it isn't called? For example, for a block with Sync input and Async output, is processBulk called if there are zero items available in the input?

The user can change the sample ratio iteration-to-iteration using input.consume() and output.publish()

Will this stop being true for Sync ports? The kind of use case that I have in mind for a Sync input and Sync output block that sometimes doesn't respect its M:N ratio is a block like the GR 3.10 Symbol Sync. Usually it decimates by a fixed ratio (the GR 3.10 block allows this ratio to be a float, but for the sake of the example let's suppose the ratio is a rational number). But the decimation is done in terms of a closed loop that depends on the values of the input, so sometimes the block "slips" one input sample forward or backwards by consuming one more or one less samples than the nominal ratio would indicate.

Reading the section on "consume() / publish() Only for Async Ports", it seems that this will be the case. In this case it might perhaps be necessary to have a way for blocks with Async ports to indicate a rough input_samples/ouput_samples relation.

Specially important in this case is to clarify what happens with busy polling a processBulk() function for a block with only Async ports. If consume() and publish() are removed for Sync ports, then every block that doesn't have a definite M:N input/output relation needs to be Async-only, but most of this blocks need some input to produce some output.

@drslebedev
Copy link
Contributor Author

drslebedev commented Jun 28, 2024

If at least one Async output port is present, processBulk is always called, even if no input samples are available (unless Async output port constraints are not met, namely min/max port constraints).

We consider introducing a policy to prevent busy polling and only call processBulk for Async ports if input samples are available.

Will this stop being true for Sync ports?

Yes, because users can easily ignoring N:M ratio using consume()/publish().
If the ratio is not fixed one should use Async ports.

@daniestevez
Copy link
Contributor

Thanks for the clarifications. What I don't understand is, for a block with a single input port and a single output port, which are the differences between the cases:

  • Async input, Sync output
  • Sync input, Async output
  • Async input, Async output

Looking at the list of differences between Sync and Async ports, I see that one of them is that if there is one Async port then processBulk() is called continuously (this isn't really a port-specific property, but a block-specific property), and that N:M Resampling is not taken into account (this isn't really a property of a single port, but a property that relates an input port and an output port). Maybe these all three cases behave the same? Maybe only the Async input, Async output makes sense? The Async annotation is applied at the level of ports, but to me it seems that its semantics affect properties that belong to a larger construct than just a single port.

@drslebedev
Copy link
Contributor Author

In general, these three cases behave similarly, but there are some differences to note.

The main differences between these cases lie in how constraints are applied:

  • Sync Ports: Both port constraints and resampling constraints are applied.
  • Async Ports: Only port constraints are applied; the input/output sample ratio is undefined.

For the output port:

  • Output Sync: processBulk is invoked only if input samples are available.
  • Output Async: processBulk is invoked even if no input samples are available, which can be controlled.

Additionally, for an Async port, a user can call consume() and publish().

@daniestevez
Copy link
Contributor

For a block with a single Async input and a single Sync output, does processBulk() only get called if the input has some samples available? Wouldn't this kind of block ought to behave as a source block (so its processBulk() always gets called) which has an additional input that may or may not have samples at any point?

@drslebedev
Copy link
Contributor Author

For the output Sync port, the processBulk function is only invoked when input samples are available. Otherwise, it's unclear how many samples to provide for the Sync output port, as Sync ports do not support a publish() call.

For a Source block with an additional Async input, it seems that both the input and output need to be Async.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Status: 🔖 Selected (6)
Development

No branches or pull requests

3 participants