Skip to content

Commit d72dff0

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 5c51e87 commit d72dff0

File tree

10 files changed

+207
-100
lines changed

10 files changed

+207
-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
@@ -90,7 +90,8 @@ myhtopsources = \
9090
UptimeMeter.c \
9191
UsersTable.c \
9292
Vector.c \
93-
XUtils.c
93+
XUtils.c \
94+
generic/sort.c
9495

9596
myhtopheaders = \
9697
Action.h \
@@ -163,7 +164,8 @@ myhtopheaders = \
163164
UptimeMeter.h \
164165
UsersTable.h \
165166
Vector.h \
166-
XUtils.h
167+
XUtils.h \
168+
generic/sort.h
167169

168170
# Linux
169171
# -----

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: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
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+
typedef struct MergeSortContext_ {
17+
size_t elementSize;
18+
Object_Compare compare;
19+
void* compareContext;
20+
char* array;
21+
} MergeSortContext;
22+
23+
static void swapByte(char* p1, char* p2) {
24+
char temp = *p1;
25+
*p1 = *p2;
26+
*p2 = temp;
27+
}
28+
29+
static void mergeRuns(MergeSortContext* mctx, void* start, void* mid, const void* end) {
30+
size_t* size = &mctx->elementSize;
31+
32+
assert(*size > 0);
33+
assert(mid < start || (size_t)((char*)mid - (char*)start) % *size == 0);
34+
assert(end < mid || (size_t)((const char*)end - (char*)mid) % *size == 0);
35+
36+
size_t rotateSize = 0;
37+
while (true) {
38+
char* p2 = (char*)mid + rotateSize;
39+
if (p2 >= (const char*)end)
40+
break;
41+
42+
char* p1 = (char*)mid - rotateSize;
43+
if (p1 <= (char*)start)
44+
break;
45+
p1 -= *size;
46+
47+
if (mctx->compare(p1, p2, mctx->compareContext) <= 0)
48+
break;
49+
50+
rotateSize += *size;
51+
}
52+
53+
if (rotateSize == 0)
54+
return;
55+
56+
char* p1 = (char*)mid;
57+
do {
58+
p1 -= 1;
59+
swapByte(p1, p1 + rotateSize);
60+
} while (p1 + rotateSize > (char*)mid);
61+
62+
size_t rightSize = (size_t)((const char*)end - (char*)mid);
63+
64+
char* newStart;
65+
char* newMid;
66+
const char* newEnd;
67+
if ((char*)mid <= (char*)start + rightSize) {
68+
newStart = start;
69+
newMid = (char*)mid - rotateSize;
70+
newEnd = mid;
71+
start = mid;
72+
mid = (char*)mid + rotateSize;
73+
} else {
74+
newStart = mid;
75+
newMid = (char*)mid + rotateSize;
76+
newEnd = end;
77+
end = mid;
78+
mid = (char*)mid - rotateSize;
79+
}
80+
mergeRuns(mctx, newStart, newMid, newEnd);
81+
mergeRuns(mctx, start, mid, end);
82+
}
83+
84+
static void* mergeSortSubarray(MergeSortContext* mctx, void* windowEnd, void* windowStart) {
85+
size_t* size = &mctx->elementSize;
86+
char** array = &mctx->array;
87+
88+
assert(*size > 0);
89+
assert((char*)windowStart >= *array);
90+
assert((size_t)((char*)windowStart - *array) % *size == 0);
91+
92+
// A run is a sorted subarray. Each recursive call of this function keeps
93+
// the lengths of two runs. At most O(log(n)) lengths of runs will be
94+
// tracked on the call stack.
95+
char* runStart0 = (char*)windowEnd;
96+
while (runStart0 > (char*)windowStart) {
97+
assert((size_t)((char*)windowEnd - (char*)windowStart) % *size == 0);
98+
99+
char* runStart2 = runStart0;
100+
101+
while (true) {
102+
if (runStart0 <= (char*)windowStart)
103+
return windowEnd;
104+
105+
runStart0 -= *size;
106+
107+
if (runStart0 <= *array)
108+
break;
109+
110+
const char* p1 = runStart0 - *size;
111+
if (mctx->compare(p1, runStart0, mctx->compareContext) > 0) {
112+
break;
113+
}
114+
}
115+
116+
assert(runStart2 <= (char*)windowEnd);
117+
if (runStart2 >= (char*)windowEnd)
118+
continue;
119+
120+
char* runStart1 = runStart0;
121+
122+
char* nextWindow = windowStart;
123+
size_t runSize2 = (size_t)((char*)windowEnd - runStart2);
124+
// This comparison is safe against negative overflow.
125+
if ((size_t)(runStart1 - (char*)windowStart) > runSize2) {
126+
nextWindow = runStart1 - runSize2;
127+
assert(nextWindow > (char*)windowStart);
128+
}
129+
130+
runStart0 = mergeSortSubarray(mctx, runStart1, nextWindow);
131+
assert(runStart0 <= runStart1);
132+
133+
mergeRuns(mctx, runStart0, runStart1, runStart2);
134+
mergeRuns(mctx, runStart0, runStart2, windowEnd);
135+
136+
if (nextWindow == (char*)windowStart) {
137+
break;
138+
}
139+
}
140+
return runStart0;
141+
}
142+
143+
void Generic_sort(void* array, size_t len, size_t size, Object_Compare compare, void* context) {
144+
if (!size)
145+
return;
146+
147+
assert(len <= SIZE_MAX / size);
148+
149+
MergeSortContext mctx = {
150+
.elementSize = size,
151+
.compare = compare,
152+
.compareContext = context,
153+
.array = array
154+
};
155+
(void)mergeSortSubarray(&mctx, (char*)array + len * size, array);
156+
}

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)