Path: blob/main/lib/libc/tests/stdlib/qsort_bench.c
39530 views
/*-1* Copyright (c) 2025 Dag-Erling Smørgrav <[email protected]>2*3* SPDX-License-Identifier: BSD-2-Clause4*/56#include <atf-c.h>7#include <stdbool.h>8#include <stdio.h>9#include <stdlib.h>10#include <time.h>11#include <unistd.h>1213/*-14* Measures qsort(3) runtime with pathological input and verify that it15* stays close to N * log2(N).16*17* Thanks to Vivian Hussey for the proof of concept.18*19* The input we construct is similar to a sweep from 0 to N where each20* half, except for the first element, has been reversed; for instance,21* with N = 8, we get { 0, 3, 2, 1, 4, 8, 7, 6 }. This triggers a bug in22* the BSD qsort(3) where it will switch to insertion sort if the pivots23* are sorted.24*25* This article goes into more detail about the bug and its origin:26*27* https://www.raygard.net/2022/02/26/Re-engineering-a-qsort-part-328*29* With this optimization (the `if (swap_cnt == 0)` block), qsort(3) needs30* roughly N * N / 4 comparisons to sort our pathological input. Without31* it, it needs only a little more than N * log2(N) comparisons.32*/3334/* we stop testing once a single takes longer than this */35#define MAXRUNSECS 103637static bool debugging;3839static uintmax_t ncmp;4041static int42intcmp(const void *a, const void *b)43{44ncmp++;45return ((*(int *)a > *(int *)b) - (*(int *)a < *(int *)b));46}4748static void49qsort_bench(int log2n)50{51uintmax_t n = 1LLU << log2n;52int *buf;5354/* fill an array with a pathological pattern */55ATF_REQUIRE(buf = malloc(n * sizeof(*buf)));56buf[0] = 0;57buf[n / 2] = n / 2;58for (unsigned int i = 1; i < n / 2; i++) {59buf[i] = n / 2 - i;60buf[n / 2 + i] = n - i;61}6263ncmp = 0;64qsort(buf, n, sizeof(*buf), intcmp);6566/* check result and free array */67if (debugging) {68for (unsigned int i = 1; i < n; i++) {69ATF_REQUIRE_MSG(buf[i] > buf[i - 1],70"array is not sorted");71}72}73free(buf);7475/* check that runtime does not exceed N² */76ATF_CHECK_MSG(ncmp / n < n,77"runtime %ju exceeds N² for N = %ju", ncmp, n);7879/* check that runtime does not exceed N log N by much */80ATF_CHECK_MSG(ncmp / n <= log2n + 1,81"runtime %ju exceeds N log N for N = %ju", ncmp, n);82}8384ATF_TC_WITHOUT_HEAD(qsort_bench);85ATF_TC_BODY(qsort_bench, tc)86{87struct timespec t0, t1;88uintmax_t tus;8990for (int i = 10; i <= 30; i++) {91clock_gettime(CLOCK_UPTIME, &t0);92qsort_bench(i);93clock_gettime(CLOCK_UPTIME, &t1);94tus = t1.tv_sec * 1000000 + t1.tv_nsec / 1000;95tus -= t0.tv_sec * 1000000 + t0.tv_nsec / 1000;96if (debugging) {97fprintf(stderr, "N = 2^%d in %ju.%06jus\n",98i, tus / 1000000, tus % 1000000);99}100/* stop once an individual run exceeds our limit */101if (tus / 1000000 >= MAXRUNSECS)102break;103}104}105106ATF_TP_ADD_TCS(tp)107{108debugging = !getenv("__RUNNING_INSIDE_ATF_RUN") &&109isatty(STDERR_FILENO);110ATF_TP_ADD_TC(tp, qsort_bench);111return (atf_no_error());112}113114115