Skip to content

Commit 325466c

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 71721e6 commit 325466c

File tree

10 files changed

+214
-100
lines changed

10 files changed

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

0 commit comments

Comments
 (0)