Skip to content

libc: minimal: add qsort to the minimal libc #39980

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

Conversation

cfriedt
Copy link
Member

@cfriedt cfriedt commented Nov 2, 2021

This change implements qsort() for the minimal libc via Heapsort.

Heapsort time complexity is O(n log(n)) in the best, average, and worst cases. It is O(1) in space complexity (i.e. sorts in-place) and is iterative rather than recursive. Heapsort is not stable (i.e. does not preserve order of identical elements).

On cortex-m0, this implementation occupies ~240 bytes.

Fixes #28896

@github-actions github-actions bot added area: C Library C Standard Library area: Samples Samples area: Tests Issues related to a particular existing or missing test labels Nov 2, 2021
@cfriedt cfriedt marked this pull request as draft November 2, 2021 02:29
@cfriedt cfriedt force-pushed the issue/28896/add-qsort-as-libc-extension branch 3 times, most recently from ced3f83 to fd695ea Compare November 3, 2021 02:13
@github-actions github-actions bot added the area: API Changes to public APIs label Nov 3, 2021
@cfriedt cfriedt force-pushed the issue/28896/add-qsort-as-libc-extension branch 3 times, most recently from d32902a to 84b1072 Compare November 3, 2021 13:49
@cfriedt cfriedt marked this pull request as ready for review November 3, 2021 14:02
@cfriedt cfriedt force-pushed the issue/28896/add-qsort-as-libc-extension branch 5 times, most recently from 6bbb75f to cceb454 Compare November 3, 2021 14:40
@cfriedt
Copy link
Member Author

cfriedt commented Nov 3, 2021

Since this is a standard C library function that is missing from the minimal C library, I would almost consider it a bug that it is not present. Would make a lot of sense, IMHO, to add this to LTS in the v2.7-branch, as it does not break any API (and actually improves API consistence), and fixes several issues where it has been mentioned as lacking.

I would be happy to present it to the TSC and / or CRB.

@cfriedt
Copy link
Member Author

cfriedt commented Nov 3, 2021

Now as for whether qsort() should be implemented by the minimal C library, it is debatable; but, if there is enough demand for it, I am not against adding it. Its implementation, however, should be minimal such that it is very simple, low footprint and easily maintainable.

From what I've gathered, there have been a few separate issues that have called for it

After all, we are not trying to roll our own C library -- doing so would be immensely expensive and analogous to reinventing the wheel. The scope of the minimal libc should be limited to "keeping the boat afloat" and any advanced features should be provided by one of the supported "full C libraries" (e.g. newlib).

Agreed - it would be ideal if we used a single libc that would satisfy most requirements, but it's also nice to have some consistence. qsort() can be small enough that they do not add much of a footprint, and of course, if it is unused, it will be discarded by the linker.

Would it make more sense to just use Heapsort in this case? That's easily maintainable and should meet the requirements of most use cases (with the exception that it is not stable). I added the choice so that users could choose to use the bsd qsort that was under samples/ or an even smaller insertion sort if that served the purpose, but if the main purpose of "minimal" means "minimal maintenance" then Heapsort is probably a good enough solution.

As for the long-term objectives, we should look into adopting one of the existing lightweight open source C libraries to replace our current minimal C library (possibly picolibc, but we should keep searching for better alternatives).

Yes, agreed as well.

@cfriedt cfriedt force-pushed the issue/28896/add-qsort-as-libc-extension branch from cceb454 to 01bf33c Compare November 3, 2021 19:14
@cfriedt cfriedt changed the title libc: minimal: Add implementations of qsort() libc: minimal: add qsort to the minimal libc Nov 3, 2021
@cfriedt cfriedt force-pushed the issue/28896/add-qsort-as-libc-extension branch from 01bf33c to 0a2ced3 Compare November 9, 2021 01:01
Copy link
Member

@stephanosio stephanosio left a comment

Choose a reason for hiding this comment

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

Some minor comments

@cfriedt cfriedt force-pushed the issue/28896/add-qsort-as-libc-extension branch 2 times, most recently from 4dd889c to 81d1d38 Compare November 9, 2021 14:39
@cfriedt cfriedt added the backport v2.7-branch Request backport to the v2.7-branch label Nov 9, 2021
Copy link
Collaborator

@npitre npitre left a comment

Choose a reason for hiding this comment

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

You have:

/*
 * Normally parent is defined parent(k) = floor((k-1) / 2) but we can avoid a
 * divide by noticing that floor((k-1) / 2) = (k >> 1).
 */
#define parent(k) ((k) >> 1)

Here I can't ignore the fact that floor((k-1) / 2) is not equal to
(k >> 1). If used with 4, the former gives 1 while the later gives 2.

Then:

/*
 * Normally left is defined left(k) = (2 * k + 1) but we can avoid a
 * multiply by noticing that (2 * k + 1) = ((k << 1) + 1).
 */
#define left(k) (((k) << 1) + 1)

The +1 in the macro does match the comment, but it doesn't match its
parent() counterpart defined above. Incideltally, removing that +1 doesn't
appear to affect the algorithm, at least as far as the test suite is concerned.

Might be worth verifying the theory if that +1 could indeed be removed which
would 1) make the code coherent and 2) reduce the generated binary slightly.

And finally:

/*
 * Normally right is defined right(k) = (2 * (k) + 2) but we can avoid a
 * multiply by noticing that right(k) = left(k) + 1 = child + 1.
 */
#define right(root) (child + 1)

Here I really suggest you stick to left(k) + 1. It is very counter-intuitive
to have the macro argument being ignored in favor of some arbitrary variable.
And I bet the generated code will be the same anyway, or just as efficient.

@cfriedt
Copy link
Member Author

cfriedt commented Nov 9, 2021

/*
 * Normally parent is defined parent(k) = floor((k-1) / 2) but we can avoid a
 * divide by noticing that floor((k-1) / 2) = (k >> 1).
 */
#define parent(k) ((k) >> 1)

Here I can't ignore the fact that floor((k-1) / 2) is not equal to
(k >> 1). If used with 4, the former gives 1 while the later gives 2.

Good catch. Was editing slightly too hastily. It should be (k-1) >> 1.

And finally:

/*
 * Normally right is defined right(k) = (2 * (k) + 2) but we can avoid a
 * multiply by noticing that right(k) = left(k) + 1 = child + 1.
 */
#define right(root) (child + 1)

Here I really suggest you stick to left(k) + 1. It is very counter-intuitive
to have the macro argument being ignored in favor of some arbitrary variable.
And I bet the generated code will be the same anyway, or just as efficient.

Good suggestion - thanks 😊

@cfriedt cfriedt force-pushed the issue/28896/add-qsort-as-libc-extension branch from 81d1d38 to 3ed54e3 Compare November 9, 2021 22:39
@cfriedt cfriedt requested review from stephanosio and npitre November 9, 2021 23:29
This change implements qsort() for the minimal libc via Heapsort.

Heapsort time complexity is O(n log(n)) in the best, average,
and worst cases. It is O(1) in space complexity (i.e. sorts
in-place) and is iterative rather than recursive. Heapsort is
not stable (i.e. does not preserve order of identical elements).

On cortex-m0, this implementation occupies ~240 bytes.

Fixes zephyrproject-rtos#28896

Signed-off-by: Christopher Friedt <[email protected]>
This change adds tests for qsort().

Signed-off-by: Christopher Friedt <[email protected]>
@cfriedt cfriedt force-pushed the issue/28896/add-qsort-as-libc-extension branch from 3ed54e3 to 3563a01 Compare November 10, 2021 00:35
@frantony
Copy link
Contributor

I have faced with link error on experimental MIPS port after 2cf08cd merging, please see 2ef72de.

sylvioalves added a commit to sylvioalves/zephyr that referenced this pull request Nov 11, 2021
PR zephyrproject-rtos#39980 added qsort to minimal libc but caused
shell_modules sample to fail building.

Signed-off-by: Sylvio Alves <[email protected]>
nashif pushed a commit that referenced this pull request Nov 11, 2021
PR #39980 added qsort to minimal libc but caused
shell_modules sample to fail building.

Signed-off-by: Sylvio Alves <[email protected]>
zephyrbot pushed a commit that referenced this pull request Nov 11, 2021
PR #39980 added qsort to minimal libc but caused
shell_modules sample to fail building.

Signed-off-by: Sylvio Alves <[email protected]>
nashif pushed a commit that referenced this pull request Nov 30, 2021
PR #39980 added qsort to minimal libc but caused
shell_modules sample to fail building.

Signed-off-by: Sylvio Alves <[email protected]>
@cfriedt cfriedt deleted the issue/28896/add-qsort-as-libc-extension branch July 22, 2022 15:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area: API Changes to public APIs area: C Library C Standard Library area: Minimal libc Minimal C Standard Library area: Samples Samples area: Tests Issues related to a particular existing or missing test backport v2.7-branch Request backport to the v2.7-branch
Projects
None yet
Development

Successfully merging this pull request may close these issues.

add qsort as libc extension
4 participants