Skip to content

Add mutiple push and pop for circular buffer #13563

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

Merged
merged 3 commits into from
Sep 21, 2020

Conversation

paul-szczepanek-arm
Copy link
Member

@paul-szczepanek-arm paul-szczepanek-arm commented Sep 7, 2020

Summary of changes

Add functions to circular buffer to allow pushing and popping multiple items at a time.

Impact of changes

Migration actions required

Documentation

None


Pull request type

[] Patch update (Bug fix / Target update / Docs update / Test update / Refactor)
[x] Feature update (New feature / Functionality change / New API)
[] Major update (Breaking change E.g. Return code change / API behaviour change)

Test results

[] No Tests required for this change (E.g docs only update)
[] Covered by existing mbed-os tests (Greentea or Unittest)
[x] Tests / results supplied as part of this PR

Reviewers

@pan-


Copy link
Member

@pan- pan- left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks Paul, that is a useful addition. Would it be possible to preserve the doc. It has been modified for bool pop(T& data).

I'm not convinced we need an array overload AND a span overload, the span should be enough.

@kjbracey-arm What do you think of these additions ? I'm still not sure about the return of the pop(Span<T>&).

@ciarmcom
Copy link
Member

ciarmcom commented Sep 7, 2020

@paul-szczepanek-arm, thank you for your changes.
@pan- @ARMmbed/mbed-os-core @ARMmbed/mbed-os-maintainers please review.

@ciarmcom ciarmcom requested review from pan- and a team September 7, 2020 13:00
@paul-szczepanek-arm
Copy link
Member Author

paul-szczepanek-arm commented Sep 7, 2020

Thanks Paul, that is a useful addition. Would it be possible to preserve the doc. It has been modified for bool pop(T& data).

Can you please comment on the line that is wrong and point out the error? The old docs had grammar mistakes and just plain incorrect text presumably based on the original implementation.

I'm not convinced we need an array overload AND a span overload, the span should be enough.

Which one do you want me to remove? OK, I assume both pop an push

I'm still not sure about the return of the pop(Span<T>&).

You're the one who asked for it not 10 minutes ago. What do you want it to return instead?

* @return True if the buffer is empty, false if not
* @return The span with the size set to number of elements popped using the buffer passed in as the parameter.
*/
mbed::Span<T> pop(mbed::Span<T>& dest)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's usually no reason to pass spans by reference rather than value - I've seen doing so mess with code generation in practice. If you did want to pass by reference, this should be const span & - the lack of const would be getting in the way of the automatic conversion from array to span, I think.

Return type seems reasonable to me, but ARM ABI does cause crummy code generation sometimes because of it, even with inlining. So returning just popped might be more efficient, but a span return is probably more idiomatic. No strong feelings.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's already const, I don't mind what is returned by pop either so unless either of you has opinions I'm leaving it.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see, it wasn't const before the span commit, must've been a change that didn't get saved

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the feedback @kjbracey-arm .

If a span is returned then I believe it can be a const span & in input. The container in input is not altered and we return the content from the function as a result.

For compiler optimizations, I suppose one can use the non span function if it matters.

I initially asked Paul to return a span to allow writing code like this:

uint8_t chunk[50];

auto popped_chunk = queue.pop(chunk);
for (auto element : popped_chunk) {
    // do something with the element 
}

// or directly 
for (auto element : queue.pop(chunk)) {
    // do something with the element 
}

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, I'm passing it by value, thanks

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Certainly want to pass spans in by value normally. Doing it by reference isn't quite as hard to read as int * const &ptr, but it's just as weird, and does generate worse code.

I'm fine with span return.

@evedon
Copy link
Contributor

evedon commented Sep 7, 2020

@paul-szczepanek-arm Thanks for the PR. We will need tests and documentation for the new functionality.

Copy link
Contributor

@donatieng donatieng left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @paul-szczepanek-arm, would you be able to amend the unit tests for the circular buffer class?
https://github.com/ARMmbed/mbed-os/blob/master/platform/tests/UNITTESTS/CircularBuffer/test_CircularBuffer.cpp (if we can call these "tests" 😅)

@donatieng
Copy link
Contributor

Ah sorry @evedon hadn't seen you were already on the case :)

@paul-szczepanek-arm
Copy link
Member Author

Unit tests added, documentation in doxygen and example expanded to cover multiple push ARMmbed/mbed-os-examples-docs_only#120

Copy link
Contributor

@kjbracey kjbracey left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the % isn't acceptable. Had so many slow device issues with slow division in critical places.

This would be somewhat alleviated by #13289, but it's still something to consider. (Although I note that may not be coming - hadn't checked it for a while - thought it had been merged).


CounterType incrementCounter(CounterType val)
{
return (++val) % BufferSize;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If BufferSize isn't a power of two this could be very inefficient on M0-type devices - using an actual software division routine - which would be bad in an interrupt context. You're better off doing a comparison. You know val can't be greater than BufferSize to start with - the compiler doesn't.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it make sense to force a power of two for the size?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be a backwards-incompatible API change.

}

_head += written;
_head %= BufferSize;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment as for the other %.

}

_tail += data_popped;
_tail %= BufferSize;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto.

mbed::Span<T> pop(mbed::Span<T> dest)
{
CounterType popped = pop(dest.data(), dest.size());
return mbed::make_Span(dest.data(), popped);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a random note that as of current C++ version you can do return {dest.data(), popped};.

Don't know what our style view on that is. I'm conflicted.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's great to push good C++ coding standard.

https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md uses return {dest.data(), popped}; so I think it's good to stick with that.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you have a more specific reference? That's quite a big page, and can't see anything specifically discussing this case.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They don't discuss, they use the construct in code examples for std::pair here -- they don't use std::make_pair

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then it's not really a specific style judgment on how explicit or not to be, which is what I'd like to see.

And there's something of a difference - a pair is a pair, so it's "just" a tuple-type container. You're effectively filling in an aggregate from a list, which is the oldest-school use of {}.

Here, you're invoking a constructor. I always feel a bit icky when using {} on its own to invoke a constructor. Maybe I'm too old-fashioned. At least it's the most "direct" constructor. Doing return { start_ptr, end_ptr }; to use the iterator constructor would feel weirder.

Anyway, I'm broadly in favour of the more concise style; I don't like excess clutter. I think the case for it is even stronger in function parameter context, where the span could just be one of several arguments. Whereas on return you do usually have space to spare for that little type reminder.

(C++17 will change the situation again, because you no longer have to use make_Span, you can have the deduction guides for the constructor letting you do just Span(...), so the clutter of explicitness is reduced further.)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the detailed explanations :) (your comments are always great learning material for me)

One side question (also asked here, what's the difference between mbed::Span and std::span? Which one should I use in my application code? and why the S for Span?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mbed::Span is based on an old revision of the std::span proposal, the only difference - API wise - with the one in the (next!) standard is the type used to return the size of the span: it is signed in mbed, not signed on std. I believe we should make the switch to unsigned at some point. Another difference is the debug capability of the iterator, it is very limited in mbed whereas libc++ returns fat iterators in debug.

Copy link
Contributor

@kjbracey kjbracey Sep 15, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just seen Vincent's answer - this partly repeats it...

std::span is C++20, so not supported by all toolchains we support, so can't use it directly. We're also in C++14 mode, and it may not be accessible in that mode.

mbed::Span was written and added to mbed while C++20 was in draft, so has some minor differences compared to the final revision of std::span - in particular its use of ptrdiff_t instead of size_t. The capital S was to fit mbed style guidelines.

Now that C++20 has been finalised, and following other work done in this style pending full std availability, I would suggest implementing and migrating to a new mstd::span that directly matches std::span alongside the other mstd stuff in the cxxsupport directory.

The mstd types like mstd::mutex and mstd::atomic are close enough matches to std that they're drop-in replacements, possibly just needing to change 1 using directive and 1 include. (And a mstd:: identifier can be made a straight using map to a std:: one if available now or later in a toolchain).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you both (again) for the detailed answers :)

I was not aware of the mstd! This is great stuff!

*
* @return The number of elements popped.
*/
CounterType pop(T* dest, CounterType len)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Astyle should be nagging you to make this T *dest. Isn't it?

@paul-szczepanek-arm
Copy link
Member Author

Thank you, @kjbracey-arm, I've changed the counter calculation to avoid %

@@ -129,23 +131,21 @@ class CircularBuffer {
CounterType written = len;

/* on first pass we write as much as we can to the right of head */
if ((_head + len) > BufferSize) {
if ((_head + written) > BufferSize) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is the same, just changed for readability

@mergify mergify bot dismissed kjbracey’s stale review September 10, 2020 11:46

Pull request has been modified.

return (++val) % BufferSize;
val += increment;

MBED_ASSERT(val <= BufferSize);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is a private function so we can guarantee we will never exceed buffersize even after increment so we can simplify calculation

kjbracey
kjbracey previously approved these changes Sep 10, 2020
@mergify mergify bot dismissed kjbracey’s stale review September 10, 2020 12:30

Pull request has been modified.

@0xc0170
Copy link
Contributor

0xc0170 commented Sep 10, 2020

CI started

@0xc0170
Copy link
Contributor

0xc0170 commented Sep 10, 2020

allow poppiong and pushing of multiplke items in circular buf

Typo in the commit msg for the first commit. Lets fix it after CI run.

@0xc0170
Copy link
Contributor

0xc0170 commented Sep 10, 2020

Or wait, we got something in the queue, faster is if I abort now. aborting the CI

@paul-szczepanek-arm
Copy link
Member Author

fixed the typos in the commit message (I had two!)

@mergify mergify bot dismissed 0xc0170’s stale review September 10, 2020 14:03

Pull request has been modified.

@mbed-ci
Copy link

mbed-ci commented Sep 10, 2020

Jenkins CI Test : ❌ FAILED

Build Number: 2 | 🔒 Jenkins CI Job | 🌐 Logs & Artifacts

CLICK for Detailed Summary

jobs Status
jenkins-ci/mbed-os-ci_unittests
jenkins-ci/mbed-os-ci_build-GCC_ARM ✔️
jenkins-ci/mbed-os-ci_build-ARM ✔️

@0xc0170
Copy link
Contributor

0xc0170 commented Sep 15, 2020

I restarted tests

@kjbracey
Copy link
Contributor

One general thought on the class - this extension preserves the existing "silently overwrite existing data when pushing" API, which I've yet to see a use for. Over on a customer branch I added try_push as an optimisation to refuse when full, which is what BufferedSerial really needs.

When that gets merged, we'd want try_push forms of these as well, returning number of items successfully pushed.

kjbracey
kjbracey previously approved these changes Sep 15, 2020
@mbed-ci
Copy link

mbed-ci commented Sep 15, 2020

Jenkins CI Test : ❌ FAILED

Build Number: 3 | 🔒 Jenkins CI Job | 🌐 Logs & Artifacts

CLICK for Detailed Summary

jobs Status
jenkins-ci/mbed-os-ci_unittests
jenkins-ci/mbed-os-ci_build-ARM ✔️
jenkins-ci/mbed-os-ci_build-GCC_ARM ✔️

@mergify mergify bot dismissed kjbracey’s stale review September 16, 2020 10:05

Pull request has been modified.

@0xc0170
Copy link
Contributor

0xc0170 commented Sep 16, 2020

CI restarted

@mbed-ci
Copy link

mbed-ci commented Sep 16, 2020

Jenkins CI Test : ❌ FAILED

Build Number: 4 | 🔒 Jenkins CI Job | 🌐 Logs & Artifacts

CLICK for Detailed Summary

jobs Status
jenkins-ci/mbed-os-ci_unittests ✔️
jenkins-ci/mbed-os-ci_build-ARM ✔️
jenkins-ci/mbed-os-ci_build-GCC_ARM ✔️
jenkins-ci/mbed-os-ci_dynamic-memory-usage
jenkins-ci/mbed-os-ci_cloud-client-pytest ✔️
jenkins-ci/mbed-os-ci_greentea-test ✔️

@0xc0170
Copy link
Contributor

0xc0170 commented Sep 17, 2020

I had to restart entire pipeline

@mbed-ci
Copy link

mbed-ci commented Sep 17, 2020

Jenkins CI Test : ✔️ SUCCESS

Build Number: 5 | 🔒 Jenkins CI Job | 🌐 Logs & Artifacts

CLICK for Detailed Summary

jobs Status
jenkins-ci/mbed-os-ci_unittests ✔️
jenkins-ci/mbed-os-ci_build-GCC_ARM ✔️
jenkins-ci/mbed-os-ci_build-ARM ✔️
jenkins-ci/mbed-os-ci_greentea-test ✔️
jenkins-ci/mbed-os-ci_dynamic-memory-usage ✔️
jenkins-ci/mbed-os-ci_cloud-client-pytest ✔️

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

10 participants