From 0ba9651e6ecbe52ad50ea5ba3b5f325c56966a6e Mon Sep 17 00:00:00 2001 From: Sahan Fernando Date: Tue, 30 Jun 2020 23:49:44 +1000 Subject: [PATCH] LibC: Replace Berkley's qsort() with AK::dual_pivot_quick_sort() wrapper --- Libraries/LibC/qsort.cpp | 146 +++++++++++--------------- Tests/LibC/qsort-sorts-and-copies.cpp | 98 +++++++++++++++++ 2 files changed, 160 insertions(+), 84 deletions(-) create mode 100644 Tests/LibC/qsort-sorts-and-copies.cpp diff --git a/Libraries/LibC/qsort.cpp b/Libraries/LibC/qsort.cpp index 9fc104e876..fd663f8589 100644 --- a/Libraries/LibC/qsort.cpp +++ b/Libraries/LibC/qsort.cpp @@ -24,103 +24,81 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -/*- - * Copyright (c) 1980, 1983, 1990 The Regents of the University of California. - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. All advertising materials mentioning features or use of this software - * must display the following acknowledgement: - * This product includes software developed by the University of - * California, Berkeley and its contributors. - * 4. Neither the name of the University nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - */ - -#if defined(LIBC_SCCS) && !defined(lint) -static char sccsid[] = "@(#)qsort.c 5.9 (Berkeley) 2/23/91"; -#endif /* LIBC_SCCS and not lint */ - +#include +#include #include #include -static void insertion_sort(void* bot, size_t nmemb, size_t size, int (*compar)(const void*, const void*)); -static void insertion_sort_r(void* bot, size_t nmemb, size_t size, int (*compar)(const void*, const void*, void*), void* arg); +class SizedObject { +public: + SizedObject() = delete; + SizedObject(void* data, size_t size) + : m_data(data) + , m_size(size) + { + } + void* data() const { return m_data; } + size_t size() const { return m_size; } + +private: + void* m_data; + size_t m_size; +}; + +namespace AK { + +template<> +inline void swap(const SizedObject& a, const SizedObject& b) +{ + ASSERT(a.size() == b.size()); + const size_t size = a.size(); + const auto a_data = reinterpret_cast(a.data()); + const auto b_data = reinterpret_cast(b.data()); + for (auto i = 0u; i < size; ++i) { + swap(a_data[i], b_data[i]); + } +} + +} + +class SizedObjectSlice { +public: + SizedObjectSlice() = delete; + SizedObjectSlice(void* data, size_t num_elements, size_t element_size) + : m_data(data) + , m_num_elements(num_elements) + , m_element_size(element_size) + { + } + const SizedObject operator[](size_t index) + { + return { static_cast(m_data) + index * m_element_size, m_element_size }; + } + +private: + void* m_data; + size_t m_num_elements; + size_t m_element_size; +}; void qsort(void* bot, size_t nmemb, size_t size, int (*compar)(const void*, const void*)) { - if (nmemb <= 1) + if (nmemb <= 1) { return; + } - insertion_sort(bot, nmemb, size, compar); + SizedObjectSlice slice { bot, nmemb, size }; + + AK::dual_pivot_quick_sort(slice, 0, nmemb - 1, [=](const SizedObject& a, const SizedObject& b) { return compar(a.data(), b.data()) < 0; }); } void qsort_r(void* bot, size_t nmemb, size_t size, int (*compar)(const void*, const void*, void*), void* arg) { - if (nmemb <= 1) + if (nmemb <= 1) { return; - - insertion_sort_r(bot, nmemb, size, compar, arg); -} - -void insertion_sort(void* bot, size_t nmemb, size_t size, int (*compar)(const void*, const void*)) -{ - int cnt; - unsigned char ch; - char *s1, *s2, *t1, *t2, *top; - top = (char*)bot + nmemb * size; - for (t1 = (char*)bot + size; t1 < top;) { - for (t2 = t1; (t2 -= size) >= bot && compar(t1, t2) < 0;) - ; - if (t1 != (t2 += size)) { - for (cnt = size; cnt--; ++t1) { - ch = *t1; - for (s1 = s2 = t1; (s2 -= size) >= t2; s1 = s2) - *s1 = *s2; - *s1 = ch; - } - } else - t1 += size; } -} -void insertion_sort_r(void* bot, size_t nmemb, size_t size, int (*compar)(const void*, const void*, void*), void* arg) -{ - int cnt; - unsigned char ch; - char *s1, *s2, *t1, *t2, *top; - top = (char*)bot + nmemb * size; - for (t1 = (char*)bot + size; t1 < top;) { - for (t2 = t1; (t2 -= size) >= bot && compar(t1, t2, arg) < 0;) - ; - if (t1 != (t2 += size)) { - for (cnt = size; cnt--; ++t1) { - ch = *t1; - for (s1 = s2 = t1; (s2 -= size) >= t2; s1 = s2) - *s1 = *s2; - *s1 = ch; - } - } else - t1 += size; - } + SizedObjectSlice slice { bot, nmemb, size }; + + AK::dual_pivot_quick_sort(slice, 0, nmemb - 1, [=](const SizedObject& a, const SizedObject& b) { return compar(a.data(), b.data(), arg) < 0; }); } diff --git a/Tests/LibC/qsort-sorts-and-copies.cpp b/Tests/LibC/qsort-sorts-and-copies.cpp new file mode 100644 index 0000000000..c7ded6a78a --- /dev/null +++ b/Tests/LibC/qsort-sorts-and-copies.cpp @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2020, Sahan Fernando + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include + +const size_t NUM_RUNS = 100; + +struct SortableObject { + int m_key; + int m_payload; +}; + +int compare_sortable_object(const void* a, const void* b) { + const int key1 = static_cast(a)->m_key; + const int key2 = static_cast(b)->m_key; + if (key1 < key2) { + return -1; + } else if (key1 == key2) { + return 0; + } else { + return 1; + } +} + +int calc_payload_for_pos(size_t pos) { + pos *= 231; + return pos ^ (pos << 8) ^ (pos << 16) ^ (pos << 24); +} + +void shuffle_vec(Vector &test_objects) +{ + for (size_t i = 0; i < test_objects.size() * 3; ++i) { + auto i1 = rand() % test_objects.size(); + auto i2 = rand() % test_objects.size(); + swap(test_objects[i1], test_objects[i2]); + } +} + +int main() +{ + // Generate vector of SortableObjects in sorted order, with payloads determined by their sorted positions + Vector test_objects; + for (auto i = 0; i < 1024; ++i) { + test_objects.append({i * 137, calc_payload_for_pos(i)}); + } + for (size_t i = 0; i < NUM_RUNS; i++) { + // Shuffle the vector, then sort it again + shuffle_vec(test_objects); + qsort(test_objects.data(), test_objects.size(), sizeof(SortableObject), compare_sortable_object); + // Check that the objects are sorted by key + for (auto i = 0u; i + 1 < test_objects.size(); ++i) { + const auto &key1 = test_objects[i].m_key; + const auto &key2 = test_objects[i + 1].m_key; + if (key1 > key2) { + printf("\x1b[01;35mTests failed: saw key %d before key %d\n", key1, key2); + return 1; + } + } + // Check that the object's payloads have not been corrupted + for (auto i = 0u; i < test_objects.size(); ++i) { + const auto expected = calc_payload_for_pos(i); + const auto payload = test_objects[i].m_payload; + if (payload != expected) { + printf("\x1b[01;35mTests failed: expected payload %d for pos %u, got payload %d\n", expected, i, payload); + return 1; + } + } + } + printf("Tests succeeded\n"); + return 0; +}