Repository navigation
module adapter and copier performance optimization - #7909
Conversation
7395487 to
2866abd
Compare
lyakh
left a comment
There was a problem hiding this comment.
how about single-line wrappers in src/audio/source_api_helper.c and src/audio/sink_api_helper.c - they're in .c files so they're never inlined and I think some of them might be called on hot paths.
There was a problem hiding this comment.
what about setting md->state?
There was a problem hiding this comment.
seems md->state is for ipc3 special, and copier_process does not depend on this, so there is no error reported in CI and QB.
There was a problem hiding this comment.
This is a change in behaviour. In other cases functions, that you inlined, really did nothing else apart from calling a method. And if it's really only needed for IPC3, removing those lines is more difficult to verify, because CI doesn't test IPC3 any more. It seems to me that md->state is something like a debugging flag, used to verify that we aren't running various operations in a wrong state, nothing else. Hitting such a wrong state would be an error, and I don't think we're hitting those errors ever, so maybe that .state could be removed completely.
There was a problem hiding this comment.
I spent time to check, I also think this can be removed completely, let me submit a new patch to cover this.
Thanks, @lyakh
There was a problem hiding this comment.
this loses these potential errors, but yes, I can see, that possibly they aren't needed
There was a problem hiding this comment.
yeah, I did not remove it totally, we can try to remove those checks later.
SOF worry too much about each possible error and this cause much more overhead in component.
There was a problem hiding this comment.
@lyakh , I intend to remove this check, as other converter function does not have this check, what do you think?
There was a problem hiding this comment.
not related to this your PR and probably not relevant for performance, but could we make the code above more readable and less error prone by removing those mid-function jumps and rewriting it as
if (j >= IPC4_COPIER_MODULE_OUTPUT_PINS_COUNT) {
comp_err(dev, "Sink queue ID: %d >= max output pin count: %d\n",
j, IPC4_COPIER_MODULE_OUTPUT_PINS_COUNT);
ret = -EINVAL;
} else if (!converter[j]) {
comp_err(dev, "No PCM converter for sink queue %d\n", j);
ret = -EINVAL;
} else if (sink_dev && sink_dev->state == COMP_STATE_ACTIVE) {
ret = dma_buffer_copy_from_no_consume(dma_buf, sink_c,
converter[j], bytes);
}
There was a problem hiding this comment.
Those code are added may be can resolve some CI or QB failures, @ranj063 may comment on this.
Actually not only this, there is a lot of code, like below in dai_common_copy:
/* Check possibility of glitch occurrence */
if (dev->direction == SOF_IPC_STREAM_PLAYBACK &&
copy_bytes + avail_bytes < dd->period_bytes)
comp_warn(dev, "dai_common_copy(): Copy_bytes %d + avail bytes %d < period bytes %d, possible glitch",
copy_bytes, avail_bytes, dd->period_bytes);
else if (dev->direction == SOF_IPC_STREAM_CAPTURE &&
copy_bytes + free_bytes < dd->period_bytes)
comp_warn(dev, "dai_common_copy(): Copy_bytes %d + free bytes %d < period bytes %d, possible glitch",
copy_bytes, free_bytes, dd->period_bytes);
/* return if nothing to copy */
if (!copy_bytes) {
comp_warn(dev, "dai_zephyr_copy(): nothing to copy");
dma_reload(dd->chan->dma->z_dev, dd->chan->index, 0, 0, 0);
return 0;
}
I will try to remove or optimize those in next round of copier optimization.
There was a problem hiding this comment.
@btian1 this is code carried over from the previous implementation when we did the optimization or conversion to module interface. If there's scope for clean up/further optimization lets do it.
There was a problem hiding this comment.
we can try to do it next round.
3c77110 to
6fb56f9
Compare
btian1
left a comment
There was a problem hiding this comment.
inline is only used for time critical function, otherwise, it will bring extra cost in code size.
|
@btian1 can you please rebase |
8e12f35 to
ae78595
Compare
btian1
left a comment
There was a problem hiding this comment.
@kv2019i @lyakh @ranj063 @RanderWang , please continue review this PR.
There was a problem hiding this comment.
@lyakh , I intend to remove this check, as other converter function does not have this check, what do you think?
f5254cd to
0ccf11c
Compare
| #if CONFIG_IPC_MAJOR_3 | ||
| return module_process_legacy(mod, NULL, 0, NULL, 0); | ||
| #else | ||
| return mod->priv.ops->process_audio_stream(mod, NULL, 0, NULL, 0); |
There was a problem hiding this comment.
This should be 0 cost with the staic inline in the header - I know we do check the ops != NULL, so that will add cost, but we shouldcheck all mandarory ops != NULL at init and check all optional ops != NULL at every usage.
There was a problem hiding this comment.
inline module_process_legacy is not an option, due to it was called 3 times in module_adapter.c, should not inline it.
Also, not only function parameter take cycles, but also the "if/else if/else" and later "(ret && ret != -ENOSPC && ret != -ENODATA)", these are also expensive.
regarding add a check, this cost adding is quite transparent, it is acceptable, however, I do think:
https://github.com/thesofproject/sof/blob/main/src/audio/module_adapter/module_adapter.c#L1177
also did the check, right?
There was a problem hiding this comment.
This can be optimised, the md->ops will be known at init time, we dont need to check this at process time to know which ops function to call.
The return value check can also be improved by only checking (ret < 0), we should not be filtering error numbers. Likewise modules should only return -ENODATA or -ENOSPC iff these are real errors that stop the module from processing any further data (without a reset).
int module_process_legacy(struct processing_module *mod,
struct input_stream_buffer *input_buffers, int num_input_buffers,
struct output_stream_buffer *output_buffers,
int num_output_buffers)
{
struct comp_dev *dev = mod->dev;
int ret;
struct module_data *md = &mod->priv;
comp_dbg(dev, "module_process_legacy() start");
#if CONFIG_IPC_MAJOR_3
if (md->state != MODULE_IDLE) {
comp_err(dev, "module_process(): wrong state of comp_id %x, state %d",
dev_comp_id(dev), md->state);
return -EPERM;
}
/* set state to processing */
md->state = MODULE_PROCESSING;
#endif
if (md->ops->process_audio_stream)
ret = md->ops->process_audio_stream(mod, input_buffers, num_input_buffers,
output_buffers, num_output_buffers);
else if (md->ops->process_raw_data)
ret = md->ops->process_raw_data(mod, input_buffers, num_input_buffers,
output_buffers, num_output_buffers);
else
ret = -EOPNOTSUPP;
if (ret && ret != -ENOSPC && ret != -ENODATA) {
comp_err(dev, "module_process() error %d: for comp %d",
ret, dev_comp_id(dev));
return ret;
}
comp_dbg(dev, "module_process_legacy() done");
#if CONFIG_IPC_MAJOR_3
/* reset state to idle */
md->state = MODULE_IDLE;
#endif
return 0;
}and for
int module_adapter_copy(struct comp_dev *dev)
{
comp_dbg(dev, "module_adapter_copy(): start");
struct processing_module *mod = comp_get_drvdata(dev);
if (IS_PROCESSING_MODE_AUDIO_STREAM(mod))
return module_adapter_audio_stream_type_copy(dev);
if (IS_PROCESSING_MODE_RAW_DATA(mod))
return module_adapter_raw_data_type_copy(dev);
if (IS_PROCESSING_MODE_SINK_SOURCE(mod))
return module_adapter_sink_source_copy(dev);
comp_err(dev, "module_adapter_copy(): unknown processing_data_type");
return -EINVAL;
}This can be improved by moving the mode check to init time rather than process time.
There was a problem hiding this comment.
module_init already checked:
/*check interface, there must be one and only one of processing procedure */
if (!interface->init || !interface->prepare ||
!interface->reset || !interface->free ||
(!!interface->process + !!interface->process_audio_stream +
!!interface->process_raw_data != 1)) {
comp_err(dev, "module_init(): comp %d is missing mandatory interfaces",
dev_comp_id(dev));
return -EIO;
}
However, we still need classify which process is valid during each processing:
process
process_raw_data
process_audio_stream
to avoid IS_PROCESSING_MODE_AUDIO_STREAM(mod), I need add a flag in mod to indicate which process is valid.
then use switch: module_adapter_copy, like below:
switch (mod->process_module) {
case 1:
process_audio_stream;
case 2:
process_raw_data;
case 3: process;
}
something like this, any more good suggestions?
do you think it is not allowed or don't want to add more #if IPC3?
Later, I want to submit a PR to separate module_adapter.c to ipc3/ipc4, then inside #if will be totally disappear.
There was a problem hiding this comment.
Since we check for ops != NULL in init then I would expect
// header
static inline int module_process(mod, blah, blah) {
return mod->priv.ops->process_audio_stream(mod, NULL, 0, NULL, 0);
}
// C file
#if CONFIG_IPC_MAJOR_3
return module_process_legacy(mod, NULL, 0, NULL, 0);
#else
return module_process(mod, NULL, 0, NULL, 0);
#endif|
|
||
| int audio_stream_copy(const struct audio_stream __sparse_cache *source, uint32_t ioffset, | ||
| struct audio_stream __sparse_cache *sink, uint32_t ooffset, uint32_t samples) | ||
| void audio_stream_copy(const struct audio_stream __sparse_cache *source, uint32_t ioffset, |
There was a problem hiding this comment.
what about failures here ? i.e. some error occurs in a module during copy. modules should also be allowed to return 0 bytes.
There was a problem hiding this comment.
current sof implementation does not have this consideration, if search audio_stream_copy, then you will find audio_stream_copy used as a void, like below:
https://github.com/thesofproject/sof/blob/main/src/audio/eq_iir/eq_iir.c#L309
Also, copy just did as source/sink/sample indicated, if 0, then means the input copy_size is zero.
0ccf11c to
d49e67a
Compare
| buffer_release(source); | ||
|
|
||
| if (ret < 0) | ||
| return; |
There was a problem hiding this comment.
if ret < 0 we will not update stream position for kernel side, but now it is not correct
There was a problem hiding this comment.
there is no chance to get ret < 0 case, so I removed the return.
d49e67a to
7e967bc
Compare
| if (audio_stream_get_avail_samples(source) < samples + ioffset) | ||
| return -EINVAL; | ||
| if (audio_stream_get_free_samples(sink) < samples + ooffset) | ||
| return -EINVAL; |
There was a problem hiding this comment.
an example of where these PCM processing functions are called in
Line 339 in 8f60180
There was a problem hiding this comment.
I like "uniform", sof did a lot of check in many functions, hard to say those check are all must, especially those on critical timing path.
based on current sof architecture(2X period buffer size), source is checked by samples( as long as it is not zero).
for sink, I am not sure whether there is a case with multi src --> single sink, if there have, single sink buffer size still need 2X of total src, I mean, if sink have no space, there is pipeline overrun.
we can add sink check if you still feel not safe enough.
There was a problem hiding this comment.
@lgirdwood @lyakh @mmaka1 , if you think keep error handling is must for converter, I can remove this patch, and find other way to do optimization.
There was a problem hiding this comment.
kv2019i
left a comment
There was a problem hiding this comment.
I'll lift my request-changes. I have a couple of minor nitpicks left, but nothing to block merge. The changes to drop "int" returns still seem quite scary and it's not so obvious that we can live withotu the capabillity to return from errors, but your point this is not universally followed, does make sense, so I'm ok to proceed.
| if (audio_stream_get_avail_samples(source) < samples + ioffset) | ||
| return -EINVAL; | ||
| if (audio_stream_get_free_samples(sink) < samples + ooffset) | ||
| return -EINVAL; |
7e967bc to
7628be0
Compare
| #if CONFIG_IPC_MAJOR_3 | ||
| return module_process_legacy(mod, NULL, 0, NULL, 0); | ||
| #else | ||
| return mod->priv.ops->process_audio_stream(mod, NULL, 0, NULL, 0); |
There was a problem hiding this comment.
Since we check for ops != NULL in init then I would expect
// header
static inline int module_process(mod, blah, blah) {
return mod->priv.ops->process_audio_stream(mod, NULL, 0, NULL, 0);
}
// C file
#if CONFIG_IPC_MAJOR_3
return module_process_legacy(mod, NULL, 0, NULL, 0);
#else
return module_process(mod, NULL, 0, NULL, 0);
#endif2313e31 to
195032d
Compare
create new module process stream function for host and dai audio stream handling. Signed-off-by: Baofeng Tian <baofeng.tian@intel.com>
Save one more wrapper time for host copier. Signed-off-by: Baofeng Tian <baofeng.tian@intel.com>
Use small tricks to make dai have better performance, with this patch, dai single playback performance back to origin state(before convert to module interface). Signed-off-by: Baofeng Tian <baofeng.tian@intel.com>
b7a2827 to
9921f85
Compare
|
SOFCI TEST |
| * \brief Module-proces type | ||
| */ | ||
| enum module_process_type { | ||
| MODULE_PROCESS_STREAM, |
There was a problem hiding this comment.
Can we have some inline comments describing each type with examples and how to categorize. Can be in another PR .
There was a problem hiding this comment.
yes, I removed this patch, only keep 3 patches for this PR, and will submit new PR later.
9921f85 to
195032d
Compare
this PR did following things:

optimize module adapter and copier, reduce host and dai performance increase caused by #7890.
below is the rough result: