Skip to content

Commit 0c6d188

Browse files
committed
New Vector_sort function, replacing insertion and quick sort
Introduce the new Vector_sort() function and obsolete the old Vector_quickSort() and Vector_insertionSort() APIs. This new sort function is a natural, in-place merge sort. I.e. it takes advantage of partially sorted data, and it's stable. Space complexity: O(log(n)) worst case (on stack, no malloc()) Time complexity: O(n) best case, O(n*(log(n))^2) average & worst case Signed-off-by: Kang-Che Sung <explorer09@gmail.com>
1 parent a554473 commit 0c6d188

File tree

10 files changed

+210
-100
lines changed

10 files changed

+210
-100
lines changed

Action.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -542,7 +542,7 @@ static Htop_Reaction actionFilterByUser(State* st) {
542542
Panel_setHeader(usersPanel, "Show processes of:");
543543
Machine* host = st->host;
544544
UsersTable_foreach(host->usersTable, addUserToVector, usersPanel);
545-
Vector_insertionSort(usersPanel->items, NULL, usersPanel);
545+
Vector_sort(usersPanel->items, NULL, usersPanel);
546546
ListItem* allUsers = ListItem_new("All users", -1);
547547
Panel_insert(usersPanel, 0, (Object*) allUsers);
548548
const ListItem* picked = (ListItem*) Action_pickFromVector(st, usersPanel, 19, false);

EnvScreen.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,8 @@ static void EnvScreen_scan(InfoScreen* this) {
5252
InfoScreen_addLine(this, "Could not read process environment.");
5353
}
5454

55-
Vector_insertionSort(this->lines, NULL, this);
56-
Vector_insertionSort(panel->items, NULL, this);
55+
Vector_sort(this->lines, NULL, this);
56+
Vector_sort(panel->items, NULL, this);
5757
Panel_setSelected(panel, idx);
5858
}
5959

Makefile.am

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,8 @@ myhtopsources = \
9191
UptimeMeter.c \
9292
UsersTable.c \
9393
Vector.c \
94-
XUtils.c
94+
XUtils.c \
95+
generic/sort.c
9596

9697
myhtopheaders = \
9798
Action.h \
@@ -164,7 +165,8 @@ myhtopheaders = \
164165
UptimeMeter.h \
165166
UsersTable.h \
166167
Vector.h \
167-
XUtils.h
168+
XUtils.h \
169+
generic/sort.h
168170

169171
# Linux
170172
# -----

OpenFilesScreen.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -300,8 +300,8 @@ static void OpenFilesScreen_scan(InfoScreen* super) {
300300
OpenFiles_Data_clear(&pdata->data);
301301
}
302302
free(pdata);
303-
Vector_insertionSort(super->lines, NULL, super);
304-
Vector_insertionSort(panel->items, NULL, super);
303+
Vector_sort(super->lines, NULL, super);
304+
Vector_sort(panel->items, NULL, super);
305305
Panel_setSelected(panel, idx);
306306
}
307307

ProcessLocksScreen.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,8 +91,8 @@ static void ProcessLocksScreen_scan(InfoScreen* this) {
9191
}
9292
}
9393
free(pdata);
94-
Vector_insertionSort(this->lines, NULL, this);
95-
Vector_insertionSort(panel->items, NULL, this);
94+
Vector_sort(this->lines, NULL, this);
95+
Vector_sort(panel->items, NULL, this);
9696
Panel_setSelected(panel, idx);
9797
}
9898

Table.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ static void Table_buildTree(Table* this) {
168168
}
169169

170170
// Sort by known parent (roots first), then row ID
171-
Vector_quickSort(this->rows, compareRowByKnownParentThenNatural, this);
171+
Vector_sort(this->rows, compareRowByKnownParentThenNatural, this);
172172

173173
// Find all processes whose parent is not visible
174174
for (int i = 0; i < vsize; i++) {
@@ -199,7 +199,7 @@ void Table_updateDisplayList(Table* this) {
199199
Table_buildTree(this);
200200
} else {
201201
if (this->needsSort)
202-
Vector_insertionSort(this->rows, NULL, this);
202+
Vector_sort(this->rows, NULL, this);
203203
Vector_prune(this->displayList);
204204
int size = Vector_size(this->rows);
205205
for (int i = 0; i < size; i++)

Vector.c

Lines changed: 20 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,19 @@ in the source distribution for its full text.
1010
#include "Vector.h"
1111

1212
#include <assert.h>
13+
#include <stdint.h>
1314
#include <stdlib.h>
1415
#include <string.h>
1516

1617
#include "XUtils.h"
18+
#include "generic/sort.h"
1719

1820

21+
typedef struct VectorSortContext_ {
22+
Object_Compare compare;
23+
void* compareContext;
24+
} VectorSortContext;
25+
1926
Vector* Vector_new(const ObjectClass* type, bool owner, int size) {
2027
Vector* this;
2128

@@ -93,97 +100,24 @@ void Vector_prune(Vector* this) {
93100
memset(this->array, '\0', this->arraySize * sizeof(Object*));
94101
}
95102

96-
//static int comparisons = 0;
97-
98-
static void swap(Object** array, int indexA, int indexB) {
99-
assert(indexA >= 0);
100-
assert(indexB >= 0);
101-
Object* tmp = array[indexA];
102-
array[indexA] = array[indexB];
103-
array[indexB] = tmp;
104-
}
105-
106-
static int partition(Object** array, int left, int right, int pivotIndex, Object_Compare compare, void* context) {
107-
const Object* pivotValue = array[pivotIndex];
108-
swap(array, pivotIndex, right);
109-
int storeIndex = left;
110-
for (int i = left; i < right; i++) {
111-
//comparisons++;
112-
if (compare(array[i], pivotValue, context) <= 0) {
113-
swap(array, i, storeIndex);
114-
storeIndex++;
115-
}
116-
}
117-
swap(array, storeIndex, right);
118-
return storeIndex;
119-
}
120-
121-
static void quickSort(Object** array, int left, int right, Object_Compare compare, void* context) {
122-
if (left >= right)
123-
return;
124-
125-
int pivotIndex = left + (right - left) / 2;
126-
int pivotNewIndex = partition(array, left, right, pivotIndex, compare, context);
127-
quickSort(array, left, pivotNewIndex - 1, compare, context);
128-
quickSort(array, pivotNewIndex + 1, right, compare, context);
129-
}
130-
131-
// If I were to use only one sorting algorithm for both cases, it would probably be this one:
132-
/*
133-
134-
static void combSort(Object** array, int left, int right, Object_Compare compare, void* context) {
135-
int gap = right - left;
136-
bool swapped = true;
137-
while ((gap > 1) || swapped) {
138-
if (gap > 1) {
139-
gap = (int)((double)gap / 1.247330950103979);
140-
}
141-
swapped = false;
142-
for (int i = left; gap + i <= right; i++) {
143-
comparisons++;
144-
if (compare(array[i], array[i+gap], context) > 0) {
145-
swap(array, i, i+gap);
146-
swapped = true;
147-
}
148-
}
149-
}
150-
}
151-
152-
*/
103+
ATTR_NONNULL
104+
static int Vector_sortCompare(const void* p1, const void* p2, void* context) {
105+
VectorSortContext* vc = (VectorSortContext*) context;
153106

154-
static void insertionSort(Object** array, int left, int right, Object_Compare compare, void* context) {
155-
for (int i = left + 1; i <= right; i++) {
156-
Object* t = array[i];
157-
int j = i - 1;
158-
while (j >= left) {
159-
//comparisons++;
160-
if (compare(array[j], t, context) <= 0)
161-
break;
162-
163-
array[j + 1] = array[j];
164-
j--;
165-
}
166-
array[j + 1] = t;
167-
}
107+
return vc->compare(*(const void* const*)p1, *(const void* const*)p2, vc->compareContext);
168108
}
169109

170-
void Vector_quickSort(Vector* this, Object_Compare compare, void* context) {
171-
if (!compare) {
172-
assert(this->type->compare);
173-
compare = this->type->compare;
174-
}
175-
assert(Vector_isConsistent(this));
176-
quickSort(this->array, 0, this->items - 1, compare, context);
110+
ATTR_NONNULL_N(1)
111+
void Vector_sort(Vector* this, Object_Compare compare, void* context) {
112+
VectorSortContext vc = {
113+
.compare = compare ? compare : this->type->compare,
114+
.compareContext = context ? context : this,
115+
};
116+
assert(vc.compare);
177117
assert(Vector_isConsistent(this));
178-
}
179118

180-
void Vector_insertionSort(Vector* this, Object_Compare compare, void* context) {
181-
if (!compare) {
182-
assert(this->type->compare);
183-
compare = this->type->compare;
184-
}
185-
assert(Vector_isConsistent(this));
186-
insertionSort(this->array, 0, this->items - 1, compare, context);
119+
Generic_sort(this->array, this->items, sizeof(*this->array), Vector_sortCompare, &vc);
120+
187121
assert(Vector_isConsistent(this));
188122
}
189123

Vector.h

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,7 @@ void Vector_delete(Vector* this);
3535

3636
void Vector_prune(Vector* this);
3737

38-
void Vector_quickSort(Vector* this, Object_Compare compare, void* context);
39-
40-
void Vector_insertionSort(Vector* this, Object_Compare compare, void* context);
38+
void Vector_sort(Vector* this, Object_Compare compare, void* context);
4139

4240
void Vector_insert(Vector* this, int idx, void* data_);
4341

generic/sort.c

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
/*
2+
htop - generic/sort.c
3+
(C) 2025 htop dev team
4+
Released under the GNU GPLv2+, see the COPYING file
5+
in the source distribution for its full text.
6+
*/
7+
8+
#include "config.h" // IWYU pragma: keep
9+
10+
#include "generic/sort.h"
11+
12+
#include <assert.h>
13+
#include <stdbool.h>
14+
#include <stdint.h> // IWYU pragma: keep
15+
16+
17+
static void swapByte(char* p1, char* p2) {
18+
char temp = *p1;
19+
*p1 = *p2;
20+
*p2 = temp;
21+
}
22+
23+
static void rotate(void* buffer, size_t leftSize, size_t rightSize) {
24+
if (rightSize == 0)
25+
return;
26+
27+
char* p1 = buffer;
28+
char* p2 = p1 + leftSize;
29+
char* mid = p2;
30+
const char* const end = mid + rightSize;
31+
32+
while (true) {
33+
// Ensure there is no arithmetic overflow on input.
34+
assert(p1 <= mid);
35+
assert(mid <= p2);
36+
assert(p2 <= end);
37+
38+
if (p2 >= end) {
39+
assert(mid < end);
40+
p2 = mid;
41+
}
42+
43+
if (p1 >= p2)
44+
break;
45+
46+
if (p1 >= mid)
47+
mid = p2;
48+
49+
swapByte(p1, p2);
50+
p1 += 1;
51+
p2 += 1;
52+
}
53+
}
54+
55+
static void mergeRuns(void* array, size_t leftLen, size_t rightLen, size_t size, Object_Compare compare, void* context) {
56+
assert(size > 0);
57+
if (leftLen == 0 || rightLen == 0 || size == 0)
58+
return;
59+
60+
assert(leftLen <= SIZE_MAX / size);
61+
assert(rightLen <= SIZE_MAX / size);
62+
63+
char* p1 = array;
64+
char* p2 = p1 + leftLen * size;
65+
char* mid = p2;
66+
const char* const end = mid + rightLen * size;
67+
68+
for (size_t limit = (leftLen + rightLen) / 2; limit > 0; limit--) {
69+
// Ensure there is no arithmetic overflow on input.
70+
assert(p1 <= mid);
71+
assert(mid <= p2);
72+
assert(p2 <= end);
73+
74+
if (p1 >= mid || p2 >= end)
75+
break;
76+
77+
if (compare(p1, p2, context) <= 0) {
78+
p1 += size;
79+
} else {
80+
p2 += size;
81+
}
82+
}
83+
84+
rotate(p1, (size_t)(mid - p1), (size_t)(p2 - mid));
85+
86+
leftLen = (size_t)(p1 - (char*)array) / size;
87+
rightLen = (size_t)(p2 - mid) / size;
88+
mergeRuns(array, leftLen, rightLen, size, compare, context);
89+
90+
leftLen = (size_t)(mid - p1) / size;
91+
rightLen = (size_t)(end - p2) / size;
92+
mergeRuns(p1 + (p2 - mid), leftLen, rightLen, size, compare, context);
93+
}
94+
95+
static size_t mergeSortSubarray(void* array, size_t unsortedLen, size_t limit, size_t size, Object_Compare compare, void* context) {
96+
assert(size > 0);
97+
if (size == 0)
98+
return 0;
99+
100+
// The initial level of this function call must set "limit" to 0. Subsequent
101+
// levels of recursion will have "limit" no less than the previous level.
102+
103+
// A run is a sorted subarray. Each recursive call of this function keeps
104+
// the lengths of two runs. At most O(log(n)) lengths of runs will be
105+
// tracked on the call stack.
106+
size_t runLen[3] = {0};
107+
while (unsortedLen > 0) {
108+
size_t totalLen = unsortedLen;
109+
assert(totalLen <= SIZE_MAX / size);
110+
while (true) {
111+
--unsortedLen;
112+
113+
const char* p2 = (const char*)array + unsortedLen * size;
114+
// Ensure there is no arithmetic overflow on input.
115+
assert(p2 >= (const char*)array);
116+
117+
if (unsortedLen < limit)
118+
return 0;
119+
120+
if (unsortedLen == 0 || compare(p2 - 1 * size, p2, context) > 0) {
121+
break;
122+
}
123+
}
124+
runLen[1] = totalLen - unsortedLen;
125+
126+
bool reachesLimit = false;
127+
128+
assert(runLen[2] > 0 || runLen[0] == 0);
129+
if (runLen[2] > 0) {
130+
size_t nextLimit = limit;
131+
if (unsortedLen > runLen[2] + limit) {
132+
nextLimit = unsortedLen - runLen[2];
133+
} else {
134+
reachesLimit = true;
135+
}
136+
137+
runLen[0] = mergeSortSubarray(array, unsortedLen, nextLimit, size, compare, context);
138+
unsortedLen -= runLen[0];
139+
140+
char* p1 = (char*)array + unsortedLen * size;
141+
mergeRuns(p1, runLen[0], runLen[1], size, compare, context);
142+
runLen[1] += runLen[0];
143+
runLen[0] = 0;
144+
145+
mergeRuns(p1, runLen[1], runLen[2], size, compare, context);
146+
}
147+
runLen[2] += runLen[1];
148+
runLen[1] = 0;
149+
150+
if (reachesLimit) {
151+
break;
152+
}
153+
}
154+
return runLen[2];
155+
}
156+
157+
void Generic_sort(void* array, size_t len, size_t size, Object_Compare compare, void* context) {
158+
(void)mergeSortSubarray(array, len, 0, size, compare, context);
159+
}

generic/sort.h

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
#ifndef HEADER_sort
2+
#define HEADER_sort
3+
/*
4+
htop - generic/sort.h
5+
(C) 2025 htop dev team
6+
Released under the GNU GPLv2+, see the COPYING file
7+
in the source distribution for its full text.
8+
*/
9+
10+
#include <stddef.h>
11+
12+
#include "Object.h"
13+
14+
15+
void Generic_sort(void* array, size_t len, size_t size, Object_Compare compare, void* context);
16+
17+
#endif

0 commit comments

Comments
 (0)