From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from mail-pg1-f175.google.com (mail-pg1-f175.google.com [209.85.215.175]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id C43C53750AC for ; Tue, 4 Aug 2026 23:35:08 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.215.175 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1785886510; cv=none; b=HdJ99D2jJ1dOjxDX5qtouMtneI8U6ygBaWKhNyB/+mLBdLCsGLVS0LvCJgcTO1Ap4TZoz/5Q1R7nSnKMRdFZN8hGVDuKsF/EM7aTPnjybQLL9uzKezASyE8HE14WXMErKAymApSp8bM+1t9lFPFEOZ/U/Kd7UP3Yr6MaRce6dOc= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1785886510; c=relaxed/simple; bh=QNGlFtMkZ8RySaU4hJdGePqPzpM936FdeUILigNdiuQ=; h=From:To:Cc:Subject:Date:Message-ID:MIME-Version:Content-Type; b=EtZYKRaT7D0gzom5i0/FplzsGB9f+dvX/8XYo2MzrIiCQnMyIwz01ixWyjhbRIJIP06/QWlpiYNoATfCjgNvRPWf2uUV3G4nsDfREoHnYdLZj5O5kA4Qyg+Y2a5sDZLSn4zxLZLc7yJWFFaLlmDbddCeDU4MmQazir9aFyHaVlE= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=lrmkt1i3; arc=none smtp.client-ip=209.85.215.175 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="lrmkt1i3" Received: by mail-pg1-f175.google.com with SMTP id 41be03b00d2f7-c9e30214d8fso284673a12.3 for ; Tue, 04 Aug 2026 16:35:08 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1785886508; x=1786491308; darn=vger.kernel.org; h=content-transfer-encoding:content-type:mime-version:message-id:date :subject:cc:to:from:from:to:cc:subject:date:message-id:reply-to :content-type; bh=0POSZIO9vBUBBJheUHlr+Fj4zHpVqcvv19RHkPl4Wh0=; b=lrmkt1i3NjQXY90W5KXEIoV6vo1sJONljleZnwSRGwaQy7gQzSPIkeOiQN15/c0wv9 MsrwgylroWeCEOFgnSYi7EgQ2wZqxzWVX1ylHZ5nSb8x32+T+psMdViRMzW/GG1+h8Gh 1aVrajsn/91tJLWme5czyYZW3X+XW7CMtmgv0UCaCN03c2lR81RrdN89YzdRl94NiPVa OOLwZjAJEkgVcwXlqyIsA6oOG6G+oNerPGQScdm5Dl6he9Xd4+bmEHP7w21jwWMGccJA 9Hz8dpynpvoe8WQNIigqNz9wF04nohE/cUnkwTIV1RUijbZ/A/dJWFYTvqgJGV74TpA/ g42Q== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1785886508; x=1786491308; h=content-transfer-encoding:content-type:mime-version:message-id:date :subject:cc:to:from:x-gm-gg:x-gm-message-state:from:to:cc:subject :date:message-id:reply-to:content-type; bh=0POSZIO9vBUBBJheUHlr+Fj4zHpVqcvv19RHkPl4Wh0=; b=VxRPOarZKIgmpTaomIzW/ik+waM25nV22dYJkIgKRurqzojymjKkeSCY3LBKzI0NII Wyw1GARH6fzzKo7hqodaze+vcCT+66bGixQVPFEAhHSTNJxg9pWljHCTgf10N5aHYwdU SvLUYqLkn2q1yZuErDQh4Ve/xcp+nt6IQkRZtmP1XJ5inb0pNUgT32KBOb6fkJJDM33t cbbvUvGCbIoSwP05Gtw7QIZo0z+5q8Q6Gwdamaj1d6VZi8A/NL595e1nH16oavGWAA95 dRP5SXpRkKpn9IuuKYgC3W07mxJjwiIb/lW4I/eQC5D/qm18u1I8TP3Wnb4FiFnZCkJP NzHQ== X-Gm-Message-State: AOJu0YytPFlQ1dm2vhYQE5MLEf/5xsC59MzmfrHdqp/hjt7kUZNU0VgI 9mZBEdg5lRJR6cw3JVn3C8NNgkykIIAKXjwVFeAVokmcLRKW96vouOk+ X-Gm-Gg: AR+sD10nMJEnMd5DCFBJYZOA7+h4YJqo7RsiX2DmYqqcYjcHdDt+tOHoCCVaIaVJcan B8PCxPSN7hQxLmGeKhe3hJmMG+XCa3r7Pae6DZqdJgkJ+OC2+ReYZWnlH1R/3FlehaaP7uZTVrH 6y9RhqgqYjRyHS/EqXse+IWmnyoKnL+jdUkecDZPeTNyAJj9Q3DUD1WLtVQVkDG1Ch1t80BbgdS +z7M4yJL9pIKs4fneT0ZoVvy+7dxMj9qhke65YYUTkAisQp/hNoqdpCZSdgaQ+8x2Oulqitj57v aCcQELWtisgRn5XGMDd2unc+UeJtQo3/Q22QNJo2lwJmOpN0iPjirLjPewHrM0+WaoDdETDHeUw TL/eutC6DjbdaqjnkZZNJ8oiwYwevLCJAUjCOJTIdZjew9utW7ifB0kJ9N/qAzcZjU1KdnFUKcL v3H1WGc5bXhIUGIT2/zbK4+D99pHyj4G5t5HtRo2a3E1YhYDGbYIpeQKXWjvayZEdQJPGgfuf1Z R+ERfC1lyE78LmjXGNwc0oge8TfdthU/yIrIIg= X-Received: by 2002:a05:6a21:4685:b0:3c4:3321:4fe8 with SMTP id adf61e73a8af0-3cb85dbd1ffmr2636696637.14.1785886507821; Tue, 04 Aug 2026 16:35:07 -0700 (PDT) Received: from pepe-82vg (c-73-71-208-212.hsd1.ca.comcast.net. [73.71.208.212]) by smtp.gmail.com with ESMTPSA id 5a478bee46e88-3158644a195sm8351995eec.13.2026.08.04.16.35.05 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 04 Aug 2026 16:35:07 -0700 (PDT) From: Peter Taraba To: akpm@linux-foundation.org Cc: linux-kernel@vger.kernel.org, Peter Taraba Subject: [PATCH] lib: add statsort(), a distribution/bucket sort Date: Tue, 4 Aug 2026 16:34:49 -0700 Message-ID: <20260804233449.70815-1-peter.schuster.taraba@gmail.com> X-Mailer: git-send-email 2.55.0 Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds statsort(), a bucket/distribution sort for arrays keyed by a signed 64-bit value. Recursively distributes elements into sqrt(n) buckets based on linear interpolation of the key over the current range, narrowing the range at each level. Falls back to sort_r() (lib/sort.c) for small ranges, skewed/degenerate key distributions, and allocation failure, so worst-case behavior is never worse than sort_r() plus O(n) bucketing overhead. Benchmarked against sort() and glibc qsort() on uniform and Gaussian-distributed long arrays: ❯ ./run.sh Compiling... Running... Running edge-case checks... Edge cases OK. == n = 10000 distribution = uniform == statsort_longs best of 5: 0.0004 s ( 37.04 ns/elem) sort (kernel) best of 5: 0.0008 s ( 78.48 ns/elem) qsort (libc) best of 5: 0.0007 s ( 74.27 ns/elem) == n = 10000 distribution = gaussian == statsort_longs best of 5: 0.0004 s ( 39.69 ns/elem) sort (kernel) best of 5: 0.0008 s ( 77.78 ns/elem) qsort (libc) best of 5: 0.0007 s ( 74.42 ns/elem) == n = 10000 distribution = duplicates == statsort_longs best of 5: 0.0008 s ( 78.19 ns/elem) sort (kernel) best of 5: 0.0007 s ( 74.88 ns/elem) qsort (libc) best of 5: 0.0005 s ( 47.52 ns/elem) == n = 10000 distribution = small_range == statsort_longs best of 5: 0.0006 s ( 55.06 ns/elem) sort (kernel) best of 5: 0.0008 s ( 77.46 ns/elem) qsort (libc) best of 5: 0.0006 s ( 61.69 ns/elem) == n = 10000 distribution = sorted == statsort_longs best of 5: 0.0002 s ( 24.67 ns/elem) sort (kernel) best of 5: 0.0007 s ( 73.20 ns/elem) qsort (libc) best of 5: 0.0002 s ( 17.29 ns/elem) == n = 10000 distribution = reversed == statsort_longs best of 5: 0.0004 s ( 39.31 ns/elem) sort (kernel) best of 5: 0.0007 s ( 73.46 ns/elem) qsort (libc) best of 5: 0.0002 s ( 22.86 ns/elem) == n = 10000 distribution = constant == statsort_longs best of 5: 0.0000 s ( 1.38 ns/elem) sort (kernel) best of 5: 0.0006 s ( 62.94 ns/elem) qsort (libc) best of 5: 0.0002 s ( 17.30 ns/elem) == n = 100000 distribution = uniform == statsort_longs best of 5: 0.0042 s ( 41.62 ns/elem) sort (kernel) best of 5: 0.0112 s ( 111.84 ns/elem) qsort (libc) best of 5: 0.0093 s ( 93.15 ns/elem) == n = 100000 distribution = gaussian == statsort_longs best of 5: 0.0042 s ( 41.71 ns/elem) sort (kernel) best of 5: 0.0114 s ( 113.96 ns/elem) qsort (libc) best of 5: 0.0094 s ( 93.86 ns/elem) == n = 100000 distribution = duplicates == statsort_longs best of 5: 0.0106 s ( 105.76 ns/elem) sort (kernel) best of 5: 0.0097 s ( 96.88 ns/elem) qsort (libc) best of 5: 0.0055 s ( 54.64 ns/elem) == n = 100000 distribution = small_range == statsort_longs best of 5: 0.0080 s ( 80.38 ns/elem) sort (kernel) best of 5: 0.0101 s ( 100.98 ns/elem) qsort (libc) best of 5: 0.0070 s ( 70.33 ns/elem) == n = 100000 distribution = sorted == statsort_longs best of 5: 0.0033 s ( 33.44 ns/elem) sort (kernel) best of 5: 0.0095 s ( 94.56 ns/elem) qsort (libc) best of 5: 0.0020 s ( 20.41 ns/elem) == n = 100000 distribution = reversed == statsort_longs best of 5: 0.0040 s ( 39.83 ns/elem) sort (kernel) best of 5: 0.0094 s ( 93.55 ns/elem) qsort (libc) best of 5: 0.0027 s ( 27.24 ns/elem) == n = 100000 distribution = constant == statsort_longs best of 5: 0.0001 s ( 1.39 ns/elem) sort (kernel) best of 5: 0.0084 s ( 84.11 ns/elem) qsort (libc) best of 5: 0.0020 s ( 20.42 ns/elem) == n = 100000000 distribution = uniform == statsort_longs best of 1: 9.0722 s ( 90.72 ns/elem) sort (kernel) best of 1: 171.2293 s (1712.29 ns/elem) qsort (libc) best of 1: 18.4959 s ( 184.96 ns/elem) == n = 100000000 distribution = gaussian == statsort_longs best of 1: 8.6535 s ( 86.54 ns/elem) sort (kernel) best of 1: 170.0339 s (1700.34 ns/elem) qsort (libc) best of 1: 17.1339 s ( 171.34 ns/elem) == n = 100000000 distribution = duplicates == statsort_longs best of 1: 23.0730 s ( 230.73 ns/elem) sort (kernel) best of 1: 17.0882 s ( 170.88 ns/elem) qsort (libc) best of 1: 9.6171 s ( 96.17 ns/elem) == n = 100000000 distribution = small_range == statsort_longs best of 1: 19.4232 s ( 194.23 ns/elem) sort (kernel) best of 1: 31.0023 s ( 310.02 ns/elem) qsort (libc) best of 1: 11.5071 s ( 115.07 ns/elem) == n = 100000000 distribution = sorted == statsort_longs best of 1: 3.9397 s ( 39.40 ns/elem) sort (kernel) best of 1: 16.4800 s ( 164.80 ns/elem) qsort (libc) best of 1: 3.7129 s ( 37.13 ns/elem) == n = 100000000 distribution = reversed == statsort_longs best of 1: 5.9803 s ( 59.80 ns/elem) sort (kernel) best of 1: 19.3678 s ( 193.68 ns/elem) qsort (libc) best of 1: 5.7287 s ( 57.29 ns/elem) == n = 100000000 distribution = constant == statsort_longs best of 1: 0.1562 s ( 1.56 ns/elem) sort (kernel) best of 1: 22.3140 s ( 223.14 ns/elem) qsort (libc) best of 1: 3.7718 s ( 37.72 ns/elem) Signed-off-by: Peter Taraba --- include/linux/statsort.h | 22 +++ lib/statsort.c | 326 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 348 insertions(+) create mode 100644 include/linux/statsort.h create mode 100644 lib/statsort.c diff --git a/include/linux/statsort.h b/include/linux/statsort.h new file mode 100644 index 000000000000..877f83cd2cfa --- /dev/null +++ b/include/linux/statsort.h @@ -0,0 +1,22 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _LINUX_STATSORT_H +#define _LINUX_STATSORT_H + +#include + +/** + * typedef statsort_key_func_t - sort-key extraction callback + * @elem: pointer to the element to extract a key from + * @priv: opaque pointer, passed through from statsort()'s @priv + * + * Return: a signed 64-bit key such that, for any two elements a and + * b, (key(a) < key(b)) matches the order statsort() should produce. + */ +typedef s64 (*statsort_key_func_t)(const void *elem, const void *priv); + +void statsort(void *base, size_t num, size_t size, + statsort_key_func_t key_func, const void *priv); + +void statsort_longs(long *base, size_t num); + +#endif /* _LINUX_STATSORT_H */ diff --git a/lib/statsort.c b/lib/statsort.c new file mode 100644 index 000000000000..6cabaefa4f6f --- /dev/null +++ b/lib/statsort.c @@ -0,0 +1,326 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * A distribution (bucket) sort for the Linux kernel + * + * statsort() sorts an array of elements by a caller-supplied signed + * 64-bit key. At each level of recursion it distributes the elements + * of a range into m = isqrt(n) buckets by linearly interpolating each + * element's key over the range's [min, max) span, then recurses into + * every non-empty bucket with the range narrowed to that bucket. This + * is the same idea as radix/bucket sort: when keys are close to + * uniformly distributed, each level shrinks n by a factor of roughly + * sqrt(n), giving close to O(n) average behaviour overall. + * + * Two safety nets keep this from degrading to O(n^2) on adversarial + * or skewed input: + * + * - Ranges of STATSORT_THRESHOLD elements or fewer are finished off + * with a plain insertion sort, which is faster than recursing + * further for small n and bounds the recursion depth. + * + * - If every element in a range lands in the same bucket (the key + * distribution didn't actually discriminate between them), + * recursing again would repeat the same bucketing forever. That + * case, and any allocation failure, falls back to sort_r() (see + * lib/sort.c), which guarantees O(n log n) worst-case behaviour. + * + * All arithmetic is done with plain integer keys; the kernel has no + * business touching the FPU, so unlike a userspace bucket sort there + * is no floating-point interpolation here, only scaled 64-bit integer + * division. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Ranges at or below this size are finished off with insertion sort. */ +#define STATSORT_THRESHOLD 16 + +/* + * Bundles the key function and its private argument so they can be + * threaded through the sort_r()-based fallback path, which only has + * room for a single opaque priv pointer. + */ +struct statsort_priv { + statsort_key_func_t key; + const void *arg; +}; + +static int statsort_cmp(const void *a, const void *b, const void *priv) +{ + const struct statsort_priv *p = priv; + s64 ka = p->key(a, p->arg); + s64 kb = p->key(b, p->arg); + + if (ka < kb) + return -1; + if (ka > kb) + return 1; + return 0; +} + +/* + * Guaranteed O(n log n) fallback used for small ranges' degenerate + * siblings, allocation failure, and the single-bucket degenerate case. + */ +static void statsort_fallback(void *base, size_t num, size_t size, + statsort_key_func_t key_func, const void *priv) +{ + struct statsort_priv p = { .key = key_func, .arg = priv }; + + sort_r(base, num, size, statsort_cmp, NULL, &p); +} + +/** + * statsort_insertion - insertion sort by key, for small ranges + * @base: pointer to the range to sort + * @num: number of elements in the range + * @size: size of each element + * @key_func: pointer to the key-extraction function + * @priv: opaque pointer passed through to @key_func + * @tmp: scratch buffer of at least @size bytes, for the sifted element + * + * O(n^2) worst case, but with very low constant factors, which makes + * it faster than recursing further once a range is small. + */ +static void statsort_insertion(void *base, size_t num, size_t size, + statsort_key_func_t key_func, const void *priv, + void *tmp) +{ + size_t i, j; + + for (i = 1; i < num; i++) { + s64 key; + + memcpy(tmp, base + i * size, size); + key = key_func(tmp, priv); + + j = i; + while (j > 0 && key_func(base + (j - 1) * size, priv) > key) { + memcpy(base + j * size, base + (j - 1) * size, size); + j--; + } + memcpy(base + j * size, tmp, size); + } +} + +/* + * Map a key into one of @m buckets spanning the half-open key range + * [@min, @min + @span). @span is precomputed by the caller as an + * unsigned 64-bit quantity to sidestep signed overflow. + */ +static size_t statsort_bucket(s64 key, s64 min, u64 span, size_t m) +{ + size_t b; + + if (!span) + return 0; + + b = div64_u64((u64)(key - min) * (u64)m, span); + if (b >= m) + b = m - 1; + return b; +} + +/* + * Recursive core. @data holds the range to sort on entry; @scratch is + * a same-sized buffer that either the caller allocated (top level) or + * the previous level's own @data region (recursive levels), and ends + * up holding the sorted range, which is then copied back into @data. + */ +static void statsort_recurse(void *data, size_t num, size_t size, + s64 min, s64 max, void *scratch, + statsort_key_func_t key_func, const void *priv, + void *tmp) +{ + size_t m, i, b, nonempty; + size_t *cnt, *off, *pos; + u64 span; + + if (num <= STATSORT_THRESHOLD) { + statsort_insertion(data, num, size, key_func, priv, tmp); + return; + } + + m = int_sqrt(num); + if (!m) + m = 1; + span = (u64)(max - min); + + cnt = kcalloc(m, sizeof(*cnt), GFP_KERNEL); + if (!cnt) { + statsort_fallback(data, num, size, key_func, priv); + return; + } + + for (i = 0; i < num; i++) { + b = statsort_bucket(key_func(data + i * size, priv), min, span, m); + cnt[b]++; + } + + off = kmalloc_array(m + 1, sizeof(*off), GFP_KERNEL); + if (!off) { + kfree(cnt); + statsort_fallback(data, num, size, key_func, priv); + return; + } + off[0] = 0; + for (i = 0; i < m; i++) + off[i + 1] = off[i] + cnt[i]; + + pos = kmalloc_array(m, sizeof(*pos), GFP_KERNEL); + if (!pos) { + kfree(off); + kfree(cnt); + statsort_fallback(data, num, size, key_func, priv); + return; + } + memcpy(pos, off, m * sizeof(*pos)); + + nonempty = 0; + for (i = 0; i < m; i++) + if (cnt[i]) + nonempty++; + + for (i = 0; i < num; i++) { + b = statsort_bucket(key_func(data + i * size, priv), min, span, m); + memcpy(scratch + pos[b] * size, data + i * size, size); + pos[b]++; + } + kfree(pos); + + if (nonempty == 1) { + /* + * Every element hashed into the same bucket: the key + * range didn't discriminate between them, so recursing + * again would just repeat this bucketing forever. + */ + memcpy(data, scratch, num * size); + statsort_fallback(data, num, size, key_func, priv); + kfree(off); + kfree(cnt); + return; + } + + for (b = 0; b < m; b++) { + size_t bstart = off[b]; + size_t bsize = cnt[b]; + s64 bmin, bmax; + + if (!bsize) + continue; + + bmin = min + (s64)div_u64((u64)b * span, m); + bmax = min + (s64)div_u64((u64)(b + 1) * span, m); + + if (bsize <= STATSORT_THRESHOLD) + statsort_insertion(scratch + bstart * size, bsize, size, + key_func, priv, tmp); + else + statsort_recurse(scratch + bstart * size, bsize, size, + bmin, bmax, data + bstart * size, + key_func, priv, tmp); + } + + kfree(off); + kfree(cnt); + memcpy(data, scratch, num * size); +} + +/** + * statsort - sort an array of elements by a signed 64-bit key + * @base: pointer to data to sort + * @num: number of elements + * @size: size of each element + * @key_func: pointer to a function returning each element's sort key + * @priv: opaque pointer passed through to @key_func + * + * Sorts @num elements of @size bytes each, in ascending order of the + * key returned by key_func(elem, priv). Performs best when keys are + * close to uniformly distributed over their range, and falls back to + * sort_r()'s guaranteed O(n log n) heapsort for skewed distributions + * or on allocation failure, so worst-case behaviour is never worse + * than a plain sort_r() call plus O(n) bucketing overhead. + * + * Like sort_r(), this is not a stable sort: elements that compare + * equal may be reordered. + */ +void statsort(void *base, size_t num, size_t size, + statsort_key_func_t key_func, const void *priv) +{ + void *scratch, *tmp; + s64 min, max; + size_t i; + + if (num < 2 || !size) + return; + + min = max = key_func(base, priv); + for (i = 1; i < num; i++) { + s64 key = key_func(base + i * size, priv); + + if (key < min) + min = key; + if (key > max) + max = key; + } + + if (min == max) + return; /* all keys equal: already sorted */ + + /* + * Bucketing treats the range as the half-open interval + * [min, max), so widen max by one to keep the true maximum + * key out of the (nonexistent) bucket m. Leave it alone in + * the vanishingly unlikely case that max is already S64_MAX. + */ + if (max < S64_MAX) + max += 1; + + scratch = kmalloc_array(num, size, GFP_KERNEL); + if (!scratch) { + statsort_fallback(base, num, size, key_func, priv); + return; + } + + tmp = kmalloc(size, GFP_KERNEL); + if (!tmp) { + kfree(scratch); + statsort_fallback(base, num, size, key_func, priv); + return; + } + + statsort_recurse(base, num, size, min, max, scratch, key_func, priv, tmp); + + kfree(tmp); + kfree(scratch); +} +EXPORT_SYMBOL(statsort); + +static s64 statsort_identity_key(const void *elem, const void *priv) +{ + return *(const long *)elem; +} + +/** + * statsort_longs - sort an array of longs + * @base: pointer to the array + * @num: number of elements + * + * Convenience wrapper around statsort() for plain "long" arrays, + * mirroring the relationship between sort() and sort_r() in + * lib/sort.c. + */ +void statsort_longs(long *base, size_t num) +{ + statsort(base, num, sizeof(*base), statsort_identity_key, NULL); +} +EXPORT_SYMBOL(statsort_longs); -- 2.55.0