Coverage Report

Created: 2026-10-24 08:16

/work/toxcore/sort.c
Line
Count
Source (jump to first uncovered line)
1
/* SPDX-License-Identifier: GPL-3.0-or-later
2
 * Copyright © 2023-2026 The TokTok team.
3
 */
4
5
#include "sort.h"
6
7
#include <assert.h>
8
9
#include "attributes.h"
10
#include "ccompat.h"
11
#include "util.h"
12
13
/**
14
 * @brief Threshold for when to switch to insertion sort.
15
 *
16
 * This is a trade-off between the complexity of insertion sort and the
17
 * overhead of merge sort. The threshold is chosen to be the smallest value
18
 * that gives a measurable speedup for insertion sort over merge sort. This is
19
 * based on measurements done in sort_bench.cc. Starting from 32 elements,
20
 * merge sort is faster than insertion sort in all our tests (both unsorted
21
 * and mostly-sorted).
22
 *
23
 * Toxcore has a lot of small arrays it wants to sort, so this optimisation
24
 * makes sense.
25
 */
26
68.4k
#define SMALL_ARRAY_THRESHOLD 16
27
28
static void merge_sort_merge_back(void *_Nonnull arr, const void *_Nonnull l_arr, uint32_t l_arr_size, const void *_Nonnull r_arr, uint32_t r_arr_size, uint32_t left_start,
29
                                  const void *_Nonnull object, const Sort_Funcs *_Nonnull funcs)
30
1.02M
{
31
1.02M
    uint32_t li = 0;
32
1.02M
    uint32_t ri = 0;
33
1.02M
    uint32_t k = left_start;
34
35
6.00M
    while (li < l_arr_size && ri < r_arr_size) {
36
4.97M
        const void *l = funcs->get_callback(l_arr, li);
37
4.97M
        const void *r = funcs->get_callback(r_arr, ri);
38
        // !(r < l) <=> (r >= l) <=> (l <= r)
39
4.97M
        if (!funcs->less_callback(object, r, l)) {
40
4.12M
            funcs->set_callback(arr, k, l);
41
4.12M
            ++li;
42
4.12M
        } else {
43
845k
            funcs->set_callback(arr, k, r);
44
845k
            ++ri;
45
845k
        }
46
4.97M
        ++k;
47
4.97M
    }
48
49
    /* Copy the remaining elements of `l_arr[]`, if there are any. */
50
1.55M
    while (li < l_arr_size) {
51
522k
        funcs->set_callback(arr, k, funcs->get_callback(l_arr, li));
52
522k
        ++li;
53
522k
        ++k;
54
522k
    }
55
56
    /* Copy the remaining elements of `r_arr[]`, if there are any. */
57
3.87M
    while (ri < r_arr_size) {
58
2.84M
        funcs->set_callback(arr, k, funcs->get_callback(r_arr, ri));
59
2.84M
        ++ri;
60
2.84M
        ++k;
61
2.84M
    }
62
1.02M
}
63
64
/** Function to merge the two haves `arr[left_start..mid]` and `arr[mid+1..right_end]` of array `arr[]`. */
65
static void merge_sort_merge(void *_Nonnull arr, uint32_t left_start, uint32_t mid, uint32_t right_end, void *_Nonnull tmp, const void *_Nonnull object, const Sort_Funcs *_Nonnull funcs)
66
1.03M
{
67
1.03M
    const uint32_t l_arr_size = mid - left_start + 1;
68
1.03M
    const uint32_t r_arr_size = right_end - mid;
69
70
    /* Temporary arrays, using the tmp buffer created in `merge_sort` below. */
71
1.03M
    void *l_arr = funcs->subarr_callback(tmp, 0, l_arr_size);
72
1.03M
    void *r_arr = funcs->subarr_callback(tmp, l_arr_size, r_arr_size);
73
74
    /* Copy data to temp arrays `l_arr[]` and `r_arr[]`.
75
     *
76
     * This is iterating and repeatedly calling `get` and `set`, which sounds
77
     * slow, but is only marginally slower than having a `copy` callback. With
78
     * a `copy` callback, we'd save 3-4% in time.
79
     */
80
5.67M
    for (uint32_t i = 0; i < l_arr_size; ++i) {
81
4.64M
        funcs->set_callback(l_arr, i, funcs->get_callback(arr, left_start + i));
82
4.64M
    }
83
4.72M
    for (uint32_t i = 0; i < r_arr_size; ++i) {
84
3.69M
        funcs->set_callback(r_arr, i, funcs->get_callback(arr, mid + 1 + i));
85
3.69M
    }
86
87
    /* Merge the temp arrays back into `arr[left_start..right_end]`. */
88
1.03M
    merge_sort_merge_back(arr, l_arr, l_arr_size, r_arr, r_arr_size, left_start, object, funcs);
89
1.03M
}
90
91
static void insertion_sort_step(void *_Nonnull arr, void *_Nonnull tmp, uint32_t i, const void *_Nonnull object, const Sort_Funcs *_Nonnull funcs)
92
483k
{
93
483k
    funcs->set_callback(tmp, 0, funcs->get_callback(arr, i));
94
483k
    uint32_t j = i;
95
96
604k
    while (j > 0) {
97
583k
        if (!funcs->less_callback(object, tmp, funcs->get_callback(arr, j - 1))) {
98
462k
            break;
99
462k
        }
100
121k
        funcs->set_callback(arr, j, funcs->get_callback(arr, j - 1));
101
121k
        --j;
102
121k
    }
103
104
483k
    funcs->set_callback(arr, j, tmp);
105
483k
}
106
107
static void insertion_sort_with_buf(void *_Nonnull arr, uint32_t arr_size, void *_Nonnull tmp, uint32_t tmp_size, const void *_Nonnull object, const Sort_Funcs *_Nonnull funcs)
108
56.7k
{
109
540k
    for (uint32_t i = 1; i < arr_size; ++i) {
110
483k
        insertion_sort_step(arr, tmp, i, object, funcs);
111
483k
    }
112
56.7k
}
113
114
static bool insertion_sort(void *_Nonnull arr, uint32_t arr_size, const void *_Nonnull object, const Sort_Funcs *_Nonnull funcs)
115
56.8k
{
116
56.8k
    void *tmp = funcs->alloc_callback(object, 1);
117
118
56.8k
    if (tmp == nullptr) {
119
4
        return false;
120
4
    }
121
122
56.8k
    insertion_sort_with_buf(arr, arr_size, tmp, 1, object, funcs);
123
124
56.8k
    funcs->delete_callback(object, tmp, 1);
125
56.8k
    return true;
126
56.8k
}
127
128
void merge_sort_with_buf(void *arr, uint32_t arr_size, void *tmp, uint32_t tmp_size, const void *object, const Sort_Funcs *funcs)
129
5.80k
{
130
5.80k
    assert(tmp_size >= arr_size);
131
132
5.80k
    if (arr_size <= SMALL_ARRAY_THRESHOLD) {
133
0
        assert(tmp_size >= 1);
134
0
        insertion_sort_with_buf(arr, arr_size, tmp, tmp_size, object, funcs);
135
0
        return;
136
0
    }
137
138
    // Merge subarrays in bottom up manner.  First merge subarrays of
139
    // size 1 to create sorted subarrays of size 2, then merge subarrays
140
    // of size 2 to create sorted subarrays of size 4, and so on.
141
52.3k
    for (uint32_t curr_size = 1; curr_size <= arr_size - 1; curr_size = 2 * curr_size) {
142
        // Pick starting point of different subarrays of current size
143
1.07M
        for (uint32_t left_start = 0; left_start < arr_size - 1; left_start += 2 * curr_size) {
144
            // Find ending point of left subarray. mid+1 is starting
145
            // point of right
146
1.02M
            const uint32_t mid = min_u32(left_start + curr_size - 1, arr_size - 1);
147
1.02M
            const uint32_t right_end = min_u32(left_start + 2 * curr_size - 1, arr_size - 1);
148
149
            // Merge Subarrays arr[left_start...mid] & arr[mid+1...right_end]
150
1.02M
            merge_sort_merge(arr, left_start, mid, right_end, tmp, object, funcs);
151
1.02M
        }
152
46.5k
    }
153
5.80k
}
154
155
bool merge_sort(void *arr, uint32_t arr_size, const void *object, const Sort_Funcs *funcs)
156
62.6k
{
157
62.6k
    if (arr_size <= SMALL_ARRAY_THRESHOLD) {
158
56.8k
        return insertion_sort(arr, arr_size, object, funcs);
159
56.8k
    }
160
161
5.80k
    void *tmp = funcs->alloc_callback(object, arr_size);
162
163
5.80k
    if (tmp == nullptr) {
164
0
        return false;
165
0
    }
166
167
5.80k
    merge_sort_with_buf(arr, arr_size, tmp, arr_size, object, funcs);
168
169
5.80k
    funcs->delete_callback(object, tmp, arr_size);
170
5.80k
    return true;
171
5.80k
}