1 // SPDX-License-Identifier: GPL-2.0-only
3 * Copyright 2023 Red Hat
6 #include "radix-sort.h"
8 #include <linux/limits.h>
9 #include <linux/types.h>
11 #include "memory-alloc.h"
12 #include "string-utils.h"
15 * This implementation allocates one large object to do the sorting, which can be reused as many
16 * times as desired. The amount of memory required is logarithmically proportional to the number of
20 /* Piles smaller than this are handled with a simple insertion sort. */
21 #define INSERTION_SORT_THRESHOLD 12
23 /* Sort keys are pointers to immutable fixed-length arrays of bytes. */
24 typedef const u8
*sort_key_t
;
27 * The keys are separated into piles based on the byte in each keys at the current offset, so the
28 * number of keys with each byte must be counted.
31 /* The number of non-empty bins */
33 /* The index (key byte) of the first non-empty bin */
35 /* The index (key byte) of the last non-empty bin */
37 /* The number of occurrences of each specific byte */
42 * Sub-tasks are manually managed on a stack, both for performance and to put a logarithmic bound
43 * on the stack space needed.
46 /* Pointer to the first key to sort. */
47 sort_key_t
*first_key
;
48 /* Pointer to the last key to sort. */
50 /* The offset into the key at which to continue sorting. */
52 /* The number of bytes remaining in the sort keys. */
58 struct histogram bins
;
59 sort_key_t
*pile
[256];
60 struct task
*end_of_stack
;
61 struct task insertion_list
[256];
65 /* Compare a segment of two fixed-length keys starting at an offset. */
66 static inline int compare(sort_key_t key1
, sort_key_t key2
, u16 offset
, u16 length
)
68 return memcmp(&key1
[offset
], &key2
[offset
], length
);
71 /* Insert the next unsorted key into an array of sorted keys. */
72 static inline void insert_key(const struct task task
, sort_key_t
*next
)
74 /* Pull the unsorted key out, freeing up the array slot. */
75 sort_key_t unsorted
= *next
;
77 /* Compare the key to the preceding sorted entries, shifting down ones that are larger. */
78 while ((--next
>= task
.first_key
) &&
79 (compare(unsorted
, next
[0], task
.offset
, task
.length
) < 0))
82 /* Insert the key into the last slot that was cleared, sorting it. */
87 * Sort a range of key segments using an insertion sort. This simple sort is faster than the
88 * 256-way radix sort when the number of keys to sort is small.
90 static inline void insertion_sort(const struct task task
)
94 for (next
= task
.first_key
+ 1; next
<= task
.last_key
; next
++)
95 insert_key(task
, next
);
98 /* Push a sorting task onto a task stack. */
99 static inline void push_task(struct task
**stack_pointer
, sort_key_t
*first_key
,
100 u32 count
, u16 offset
, u16 length
)
102 struct task
*task
= (*stack_pointer
)++;
104 task
->first_key
= first_key
;
105 task
->last_key
= &first_key
[count
- 1];
106 task
->offset
= offset
;
107 task
->length
= length
;
110 static inline void swap_keys(sort_key_t
*a
, sort_key_t
*b
)
118 * Count the number of times each byte value appears in the arrays of keys to sort at the current
119 * offset, keeping track of the number of non-empty bins, and the index of the first and last
122 static inline void measure_bins(const struct task task
, struct histogram
*bins
)
127 * Subtle invariant: bins->used and bins->size[] are zero because the sorting code clears
128 * it all out as it goes. Even though this structure is re-used, we don't need to pay to
129 * zero it before starting a new tally.
131 bins
->first
= U8_MAX
;
134 for (key_ptr
= task
.first_key
; key_ptr
<= task
.last_key
; key_ptr
++) {
135 /* Increment the count for the byte in the key at the current offset. */
136 u8 bin
= (*key_ptr
)[task
.offset
];
137 u32 size
= ++bins
->size
[bin
];
139 /* Track non-empty bins. */
142 if (bin
< bins
->first
)
145 if (bin
> bins
->last
)
152 * Convert the bin sizes to pointers to where each pile goes.
154 * pile[0] = first_key + bin->size[0],
155 * pile[1] = pile[0] + bin->size[1], etc.
157 * After the keys are moved to the appropriate pile, we'll need to sort each of the piles by the
158 * next radix position. A new task is put on the stack for each pile containing lots of keys, or a
159 * new task is put on the list for each pile containing few keys.
161 * @stack: pointer the top of the stack
162 * @end_of_stack: the end of the stack
163 * @list: pointer the head of the list
164 * @pile: array for pointers to the end of each pile
165 * @bins: the histogram of the sizes of each pile
166 * @first_key: the first key of the stack
167 * @offset: the next radix position to sort by
168 * @length: the number of bytes remaining in the sort keys
170 * Return: UDS_SUCCESS or an error code
172 static inline int push_bins(struct task
**stack
, struct task
*end_of_stack
,
173 struct task
**list
, sort_key_t
*pile
[],
174 struct histogram
*bins
, sort_key_t
*first_key
,
175 u16 offset
, u16 length
)
177 sort_key_t
*pile_start
= first_key
;
180 for (bin
= bins
->first
; ; bin
++) {
181 u32 size
= bins
->size
[bin
];
183 /* Skip empty piles. */
187 /* There's no need to sort empty keys. */
189 if (size
> INSERTION_SORT_THRESHOLD
) {
190 if (*stack
>= end_of_stack
)
191 return UDS_BAD_STATE
;
193 push_task(stack
, pile_start
, size
, offset
, length
);
194 } else if (size
> 1) {
195 push_task(list
, pile_start
, size
, offset
, length
);
200 pile
[bin
] = pile_start
;
201 if (--bins
->used
== 0)
208 int uds_make_radix_sorter(unsigned int count
, struct radix_sorter
**sorter
)
211 unsigned int stack_size
= count
/ INSERTION_SORT_THRESHOLD
;
212 struct radix_sorter
*radix_sorter
;
214 result
= vdo_allocate_extended(struct radix_sorter
, stack_size
, struct task
,
215 __func__
, &radix_sorter
);
216 if (result
!= VDO_SUCCESS
)
219 radix_sorter
->count
= count
;
220 radix_sorter
->end_of_stack
= radix_sorter
->stack
+ stack_size
;
221 *sorter
= radix_sorter
;
225 void uds_free_radix_sorter(struct radix_sorter
*sorter
)
231 * Sort pointers to fixed-length keys (arrays of bytes) using a radix sort. The sort implementation
232 * is unstable, so the relative ordering of equal keys is not preserved.
234 int uds_radix_sort(struct radix_sorter
*sorter
, const unsigned char *keys
[],
235 unsigned int count
, unsigned short length
)
238 struct histogram
*bins
= &sorter
->bins
;
239 sort_key_t
**pile
= sorter
->pile
;
240 struct task
*task_stack
= sorter
->stack
;
242 /* All zero-length keys are identical and therefore already sorted. */
243 if ((count
== 0) || (length
== 0))
246 /* The initial task is to sort the entire length of all the keys. */
247 start
= (struct task
) {
249 .last_key
= &keys
[count
- 1],
254 if (count
<= INSERTION_SORT_THRESHOLD
) {
255 insertion_sort(start
);
259 if (count
> sorter
->count
)
260 return UDS_INVALID_ARGUMENT
;
263 * Repeatedly consume a sorting task from the stack and process it, pushing new sub-tasks
264 * onto the stack for each radix-sorted pile. When all tasks and sub-tasks have been
265 * processed, the stack will be empty and all the keys in the starting task will be fully
268 for (*task_stack
= start
; task_stack
>= sorter
->stack
; task_stack
--) {
269 const struct task task
= *task_stack
;
270 struct task
*insertion_task_list
;
275 measure_bins(task
, bins
);
278 * Now that we know how large each bin is, generate pointers for each of the piles
279 * and push a new task to sort each pile by the next radix byte.
281 insertion_task_list
= sorter
->insertion_list
;
282 result
= push_bins(&task_stack
, sorter
->end_of_stack
,
283 &insertion_task_list
, pile
, bins
, task
.first_key
,
284 task
.offset
+ 1, task
.length
- 1);
285 if (result
!= UDS_SUCCESS
) {
286 memset(bins
, 0, sizeof(*bins
));
290 /* Now bins->used is zero again. */
293 * Don't bother processing the last pile: when piles 0..N-1 are all in place, then
294 * pile N must also be in place.
296 end
= task
.last_key
- bins
->size
[bins
->last
];
297 bins
->size
[bins
->last
] = 0;
299 for (fence
= task
.first_key
; fence
<= end
; ) {
301 sort_key_t key
= *fence
;
304 * The radix byte of the key tells us which pile it belongs in. Swap it for
305 * an unprocessed item just below that pile, and repeat.
307 while (--pile
[bin
= key
[task
.offset
]] > fence
)
308 swap_keys(pile
[bin
], &key
);
311 * The pile reached the fence. Put the key at the bottom of that pile,
312 * completing it, and advance the fence to the next pile.
315 fence
+= bins
->size
[bin
];
319 /* Now bins->size[] is all zero again. */
322 * When the number of keys in a task gets small enough, it is faster to use an
323 * insertion sort than to keep subdividing into tiny piles.
325 while (--insertion_task_list
>= sorter
->insertion_list
)
326 insertion_sort(*insertion_task_list
);