diff --git a/Action.c b/Action.c index 81fbf6d37..59b4356ab 100644 --- a/Action.c +++ b/Action.c @@ -550,7 +550,7 @@ static Htop_Reaction actionFilterByUser(State* st) { Panel_setHeader(usersPanel, "Show processes of:"); Machine* host = st->host; UsersTable_foreach(host->usersTable, addUserToVector, usersPanel); - Vector_insertionSort(usersPanel->items); + Vector_sort(usersPanel->items, NULL, usersPanel); ListItem* allUsers = ListItem_new("All users", -1); Panel_insert(usersPanel, 0, (Object*) allUsers); const ListItem* picked = (ListItem*) Action_pickFromVector(st, usersPanel, 19, false); diff --git a/EnvScreen.c b/EnvScreen.c index 4a36b318a..0fb8fa106 100644 --- a/EnvScreen.c +++ b/EnvScreen.c @@ -52,8 +52,8 @@ static void EnvScreen_scan(InfoScreen* this) { InfoScreen_addLine(this, "Could not read process environment."); } - Vector_insertionSort(this->lines); - Vector_insertionSort(panel->items); + Vector_sort(this->lines, NULL, this); + Vector_sort(panel->items, NULL, this); Panel_setSelected(panel, idx); } diff --git a/ListItem.c b/ListItem.c index a779e1d0a..71c9435b0 100644 --- a/ListItem.c +++ b/ListItem.c @@ -59,7 +59,8 @@ void ListItem_append(ListItem* this, const char* text) { this->value[newLen] = '\0'; } -int ListItem_compare(const void* cast1, const void* cast2) { +int ListItem_compare(const void* cast1, const void* cast2, void* context) { + (void)context; const ListItem* obj1 = (const ListItem*) cast1; const ListItem* obj2 = (const ListItem*) cast2; return strcmp(obj1->value, obj2->value); diff --git a/ListItem.h b/ListItem.h index 5efe8743b..19e939c27 100644 --- a/ListItem.h +++ b/ListItem.h @@ -32,7 +32,7 @@ ListItem* ListItem_new(const char* value, int key); void ListItem_append(ListItem* this, const char* text); -int ListItem_compare(const void* cast1, const void* cast2); +int ListItem_compare(const void* cast1, const void* cast2, void* context); static inline const char* ListItem_getRef(const ListItem* this) { return this->value; diff --git a/Makefile.am b/Makefile.am index 52134df64..a96dcce55 100644 --- a/Makefile.am +++ b/Makefile.am @@ -90,7 +90,8 @@ myhtopsources = \ UptimeMeter.c \ UsersTable.c \ Vector.c \ - XUtils.c + XUtils.c \ + generic/Sort.c myhtopheaders = \ Action.h \ @@ -164,7 +165,8 @@ myhtopheaders = \ UsersTable.h \ Vector.h \ XUtils.h \ - generic/ProvideDemangle.h + generic/ProvideDemangle.h \ + generic/Sort.h if HAVE_BACKTRACE_SCREEN myhtopheaders += \ diff --git a/Object.h b/Object.h index 4d7d653ee..73734f1e6 100644 --- a/Object.h +++ b/Object.h @@ -19,7 +19,7 @@ struct Object_; typedef struct Object_ Object; typedef void(*Object_Display)(const Object*, RichString*); -typedef int(*Object_Compare)(const void*, const void*); +typedef int(*Object_Compare)(const void*, const void*, void*); typedef void(*Object_Delete)(Object*); #define Object_getClass(obj_) ((const Object*)(obj_))->klass @@ -28,7 +28,7 @@ typedef void(*Object_Delete)(Object*); #define Object_delete(obj_) (assert(Object_getClass(obj_)->delete), Object_getClass(obj_)->delete((Object*)(obj_))) #define Object_displayFn(obj_) Object_getClass(obj_)->display #define Object_display(obj_, str_) (assert(Object_getClass(obj_)->display), Object_getClass(obj_)->display((const Object*)(obj_), str_)) -#define Object_compare(obj_, other_) (assert(Object_getClass(obj_)->compare), Object_getClass(obj_)->compare((const void*)(obj_), other_)) +#define Object_compare(obj_, other_, ctx_) (assert(Object_getClass(obj_)->compare), Object_getClass(obj_)->compare((const void*)(obj_), other_, ctx_) #define Class(class_) ((const ObjectClass*)(&(class_ ## _class))) diff --git a/OpenFilesScreen.c b/OpenFilesScreen.c index f8e802b5f..4b9e143a7 100644 --- a/OpenFilesScreen.c +++ b/OpenFilesScreen.c @@ -300,8 +300,8 @@ static void OpenFilesScreen_scan(InfoScreen* super) { OpenFiles_Data_clear(&pdata->data); } free(pdata); - Vector_insertionSort(super->lines); - Vector_insertionSort(panel->items); + Vector_sort(super->lines, NULL, super); + Vector_sort(panel->items, NULL, super); Panel_setSelected(panel, idx); } diff --git a/Process.c b/Process.c index f68c305e0..070a5dd36 100644 --- a/Process.c +++ b/Process.c @@ -911,7 +911,8 @@ bool Process_rowSendSignal(Row* super, Arg sgn) { return Process_sendSignal(this, sgn); } -int Process_compare(const void* v1, const void* v2) { +int Process_compare(const void* v1, const void* v2, void* context) { + (void)context; const Process* p1 = (const Process*)v1; const Process* p2 = (const Process*)v2; @@ -928,7 +929,7 @@ int Process_compare(const void* v1, const void* v2) { return (ScreenSettings_getActiveDirection(ss) == 1) ? result : -result; } -int Process_compareByParent(const Row* r1, const Row* r2) { +int Process_compareByParent(const Row* r1, const Row* r2, void* context) { int result = SPACESHIP_NUMBER( r1->isRoot ? 0 : Row_getGroupOrParent(r1), r2->isRoot ? 0 : Row_getGroupOrParent(r2) @@ -937,7 +938,7 @@ int Process_compareByParent(const Row* r1, const Row* r2) { if (result != 0) return result; - return Process_compare(r1, r2); + return Process_compare(r1, r2, context); } int Process_compareByKey_Base(const Process* p1, const Process* p2, ProcessField key) { diff --git a/Process.h b/Process.h index 5bfdfe990..d455f02f5 100644 --- a/Process.h +++ b/Process.h @@ -231,8 +231,8 @@ typedef int32_t ProcessField; /* see ReservedField list in RowField.h */ // Implemented in platform-specific code: void Process_writeField(const Process* this, RichString* str, ProcessField field); -int Process_compare(const void* v1, const void* v2); -int Process_compareByParent(const Row* r1, const Row* r2); +int Process_compare(const void* v1, const void* v2, void* context); +int Process_compareByParent(const Row* r1, const Row* r2, void* context); void Process_delete(Object* cast); extern const ProcessFieldData Process_fields[LAST_PROCESSFIELD]; #define Process_pidDigits Row_pidDigits @@ -317,8 +317,8 @@ bool Process_rowIsVisible(const Row* super, const struct Table_* table); bool Process_rowMatchesFilter(const Row* super, const struct Table_* table); -static inline int Process_pidEqualCompare(const void* v1, const void* v2) { - return Row_idEqualCompare(v1, v2); +static inline int Process_pidEqualCompare(const void* v1, const void* v2, void* context) { + return Row_idEqualCompare(v1, v2, context); } int Process_compareByKey_Base(const Process* p1, const Process* p2, ProcessField key); diff --git a/ProcessLocksScreen.c b/ProcessLocksScreen.c index 36a37f927..3a8ec067d 100644 --- a/ProcessLocksScreen.c +++ b/ProcessLocksScreen.c @@ -91,8 +91,8 @@ static void ProcessLocksScreen_scan(InfoScreen* this) { } } free(pdata); - Vector_insertionSort(this->lines); - Vector_insertionSort(panel->items); + Vector_sort(this->lines, NULL, this); + Vector_sort(panel->items, NULL, this); Panel_setSelected(panel, idx); } diff --git a/Row.c b/Row.c index 259d1ab74..a64647e41 100644 --- a/Row.c +++ b/Row.c @@ -535,14 +535,15 @@ void Row_toggleTag(Row* this) { this->tag = !this->tag; } -int Row_compare(const void* v1, const void* v2) { +int Row_compare(const void* v1, const void* v2, void* context) { + (void)context; const Row* r1 = (const Row*)v1; const Row* r2 = (const Row*)v2; return SPACESHIP_NUMBER(r1->id, r2->id); } -int Row_compareByParent_Base(const void* v1, const void* v2) { +int Row_compareByParent_Base(const void* v1, const void* v2, void* context) { const Row* r1 = (const Row*)v1; const Row* r2 = (const Row*)v2; @@ -554,7 +555,7 @@ int Row_compareByParent_Base(const void* v1, const void* v2) { if (result != 0) return result; - return Row_compare(v1, v2); + return Row_compare(v1, v2, context); } const RowClass Row_class = { diff --git a/Row.h b/Row.h index 78629a3a5..bd58d0322 100644 --- a/Row.h +++ b/Row.h @@ -82,9 +82,9 @@ typedef bool (*Row_IsHighlighted)(const Row*); typedef bool (*Row_IsVisible)(const Row*, const struct Table_*); typedef bool (*Row_MatchesFilter)(const Row*, const struct Table_*); typedef const char* (*Row_SortKeyString)(Row*); -typedef int (*Row_CompareByParent)(const Row*, const Row*); +typedef int (*Row_CompareByParent)(const Row*, const Row*, void*); -int Row_compare(const void* v1, const void* v2); +int Row_compare(const void* v1, const void* v2, void* context); typedef struct RowClass_ { const ObjectClass super; @@ -102,7 +102,7 @@ typedef struct RowClass_ { #define Row_isVisible(r_, t_) (As_Row(r_)->isVisible ? (As_Row(r_)->isVisible(r_, t_)) : true) #define Row_matchesFilter(r_, t_) (As_Row(r_)->matchesFilter ? (As_Row(r_)->matchesFilter(r_, t_)) : false) #define Row_sortKeyString(r_) (As_Row(r_)->sortKeyString ? (As_Row(r_)->sortKeyString(r_)) : "") -#define Row_compareByParent(r1_, r2_) (As_Row(r1_)->compareByParent ? (As_Row(r1_)->compareByParent(r1_, r2_)) : Row_compareByParent_Base(r1_, r2_)) +#define Row_compareByParent(r1_, r2_, ctx_) (As_Row(r1_)->compareByParent ? (As_Row(r1_)->compareByParent(r1_, r2_, ctx_)) : Row_compareByParent_Base(r1_, r2_, ctx_)) #define ONE_K 1024UL #define ONE_M (ONE_K * ONE_K) @@ -162,7 +162,8 @@ void Row_printRate(RichString* str, double rate, bool coloring); int Row_printPercentage(float val, char* buffer, size_t n, uint8_t width, int* attr); -static inline int Row_idEqualCompare(const void* v1, const void* v2) { +static inline int Row_idEqualCompare(const void* v1, const void* v2, void* context) { + (void)context; const int p1 = ((const Row*)v1)->id; const int p2 = ((const Row*)v2)->id; return p1 != p2; /* return zero when equal */ @@ -177,6 +178,6 @@ static inline bool Row_isChildOf(const Row* this, int id) { return id == Row_getGroupOrParent(this); } -int Row_compareByParent_Base(const void* v1, const void* v2); +int Row_compareByParent_Base(const void* v1, const void* v2, void* context); #endif diff --git a/Table.c b/Table.c index 02cd98bb2..fbd2e40e4 100644 --- a/Table.c +++ b/Table.c @@ -137,8 +137,8 @@ static void Table_buildTreeBranch(Table* this, int rowid, unsigned int level, in } } -static int compareRowByKnownParentThenNatural(const void* v1, const void* v2) { - return Row_compareByParent((const Row*) v1, (const Row*) v2); +static int compareRowByKnownParentThenNatural(const void* v1, const void* v2, void* context) { + return Row_compareByParent((const Row*) v1, (const Row*) v2, context); } // Builds a sorted tree from scratch, without relying on previously gathered information @@ -168,7 +168,7 @@ static void Table_buildTree(Table* this) { } // Sort by known parent (roots first), then row ID - Vector_quickSortCustomCompare(this->rows, compareRowByKnownParentThenNatural); + Vector_sort(this->rows, compareRowByKnownParentThenNatural, this); // Find all processes whose parent is not visible for (int i = 0; i < vsize; i++) { @@ -199,7 +199,7 @@ void Table_updateDisplayList(Table* this) { Table_buildTree(this); } else { if (this->needsSort) - Vector_insertionSort(this->rows); + Vector_sort(this->rows, NULL, this); Vector_prune(this->displayList); int size = Vector_size(this->rows); for (int i = 0; i < size; i++) diff --git a/Vector.c b/Vector.c index edb89dfd6..17782d15a 100644 --- a/Vector.c +++ b/Vector.c @@ -10,12 +10,19 @@ in the source distribution for its full text. #include "Vector.h" #include +#include #include #include #include "XUtils.h" +#include "generic/Sort.h" +typedef struct VectorSortContext_ { + Object_Compare compare; + void* compareContext; +} VectorSortContext; + Vector* Vector_new(const ObjectClass* type, bool owner, int size) { Vector* this; @@ -93,91 +100,24 @@ void Vector_prune(Vector* this) { memset(this->array, '\0', this->arraySize * sizeof(Object*)); } -//static int comparisons = 0; - -static void swap(Object** array, int indexA, int indexB) { - assert(indexA >= 0); - assert(indexB >= 0); - Object* tmp = array[indexA]; - array[indexA] = array[indexB]; - array[indexB] = tmp; -} +ATTR_NONNULL +static int Vector_sortCompare(const void* p1, const void* p2, void* context) { + VectorSortContext* vc = (VectorSortContext*) context; -static int partition(Object** array, int left, int right, int pivotIndex, Object_Compare compare) { - const Object* pivotValue = array[pivotIndex]; - swap(array, pivotIndex, right); - int storeIndex = left; - for (int i = left; i < right; i++) { - //comparisons++; - if (compare(array[i], pivotValue) <= 0) { - swap(array, i, storeIndex); - storeIndex++; - } - } - swap(array, storeIndex, right); - return storeIndex; + return vc->compare(*(const void* const*)p1, *(const void* const*)p2, vc->compareContext); } -static void quickSort(Object** array, int left, int right, Object_Compare compare) { - if (left >= right) - return; - - int pivotIndex = left + (right - left) / 2; - int pivotNewIndex = partition(array, left, right, pivotIndex, compare); - quickSort(array, left, pivotNewIndex - 1, compare); - quickSort(array, pivotNewIndex + 1, right, compare); -} - -// If I were to use only one sorting algorithm for both cases, it would probably be this one: -/* - -static void combSort(Object** array, int left, int right, Object_Compare compare) { - int gap = right - left; - bool swapped = true; - while ((gap > 1) || swapped) { - if (gap > 1) { - gap = (int)((double)gap / 1.247330950103979); - } - swapped = false; - for (int i = left; gap + i <= right; i++) { - comparisons++; - if (compare(array[i], array[i+gap]) > 0) { - swap(array, i, i+gap); - swapped = true; - } - } - } -} - -*/ - -static void insertionSort(Object** array, int left, int right, Object_Compare compare) { - for (int i = left + 1; i <= right; i++) { - Object* t = array[i]; - int j = i - 1; - while (j >= left) { - //comparisons++; - if (compare(array[j], t) <= 0) - break; - - array[j + 1] = array[j]; - j--; - } - array[j + 1] = t; - } -} - -void Vector_quickSortCustomCompare(Vector* this, Object_Compare compare) { - assert(compare); - assert(Vector_isConsistent(this)); - quickSort(this->array, 0, this->items - 1, compare); +ATTR_NONNULL_N(1) +void Vector_sort(Vector* this, Object_Compare compare, void* context) { + VectorSortContext vc = { + .compare = compare ? compare : this->type->compare, + .compareContext = context ? context : this, + }; + assert(vc.compare); assert(Vector_isConsistent(this)); -} -void Vector_insertionSort(Vector* this) { - assert(this->type->compare); - assert(Vector_isConsistent(this)); - insertionSort(this->array, 0, this->items - 1, this->type->compare); + Sort_sort(this->array, this->items, sizeof(*this->array), Vector_sortCompare, &vc); + assert(Vector_isConsistent(this)); } @@ -357,7 +297,7 @@ int Vector_indexOf(const Vector* this, const void* search_, Object_Compare compa for (int i = 0; i < this->items; i++) { const Object* o = this->array[i]; assert(o); - if (compare(search, o) == 0) { + if (compare(search, o, NULL) == 0) { return i; } } diff --git a/Vector.h b/Vector.h index 95617eda9..14da71767 100644 --- a/Vector.h +++ b/Vector.h @@ -35,12 +35,7 @@ void Vector_delete(Vector* this); void Vector_prune(Vector* this); -void Vector_quickSortCustomCompare(Vector* this, Object_Compare compare); -static inline void Vector_quickSort(Vector* this) { - Vector_quickSortCustomCompare(this, this->type->compare); -} - -void Vector_insertionSort(Vector* this); +void Vector_sort(Vector* this, Object_Compare compare, void* context); void Vector_insert(Vector* this, int idx, void* data_); diff --git a/generic/Sort.c b/generic/Sort.c new file mode 100644 index 000000000..765601aca --- /dev/null +++ b/generic/Sort.c @@ -0,0 +1,167 @@ +/* +htop - generic/Sort.c +(C) 2025 htop dev team +Released under the GNU GPLv2+, see the COPYING file +in the source distribution for its full text. +*/ + +#include "config.h" // IWYU pragma: keep + +#include "generic/Sort.h" + +#include +#include +#include // IWYU pragma: keep + +typedef struct MergeSortContext_ { + /* To minimize code size, the most frequently referenced member of this + structure is ordered first. */ + size_t elementSize; + Object_Compare compare; + void* compareContext; + void* array; +} MergeSortContext; + +static void swapByte(char* p1, char* p2) { + char temp = *p1; + *p1 = *p2; + *p2 = temp; +} + +static void mergeRuns(MergeSortContext* mctx, void* start, void* mid, const void* end) { + size_t* size = &mctx->elementSize; + + assert(*size > 0); + + // This is a stable merge using rotation. Idea come from Xinok, see: + // https://xinok.wordpress.com/2014/08/17/in-place-merge-sort-demystified-2/ + while (true) { + assert(mid < start || (size_t)((char*)mid - (char*)start) % *size == 0); + assert(end < mid || (size_t)((const char*)end - (char*)mid) % *size == 0); + + size_t rotateSize = 0; + while (true) { + char* p2 = (char*)mid + rotateSize; + if (p2 >= (const char*)end) + break; + + char* p1 = (char*)mid - rotateSize; + if (p1 <= (char*)start) + break; + p1 -= *size; + + if (mctx->compare(p1, p2, mctx->compareContext) <= 0) + break; + + rotateSize += *size; + } + + if (rotateSize == 0) + return; + + char* p1 = mid; + do { + p1 -= 1; + swapByte(p1, p1 + rotateSize); + } while (p1 + rotateSize > (char*)mid); + + size_t rightSize = (size_t)((const char*)end - (char*)mid); + + void* newStart; + void* newMid; + const void* newEnd; + if ((char*)mid <= (char*)start + rightSize) { + newStart = start; + newMid = (char*)mid - rotateSize; + newEnd = mid; + start = mid; + mid = (char*)mid + rotateSize; + } else { + newStart = mid; + newMid = (char*)mid + rotateSize; + newEnd = end; + end = mid; + mid = (char*)mid - rotateSize; + } + mergeRuns(mctx, newStart, newMid, newEnd); + } +} + +static void* mergeSortSubarray(MergeSortContext* mctx, size_t nextWindowSize, void* windowStart, void* windowEnd) { + size_t* size = &mctx->elementSize; + void** array = &mctx->array; + + assert(*size > 0); + assert(windowStart >= *array); + assert((size_t)((char*)windowStart - (char*)*array) % *size == 0); + assert(windowEnd >= windowStart); + assert((size_t)((char*)windowEnd - (char*)windowStart) % *size == 0); + + // A run is a sorted subarray. Each recursive call of this function records + // the length of one run. At most O(log(n)) lengths of runs are tracked on + // the call stack. + char* newRun = (char*)windowEnd; + while (true) { + if (newRun <= (char*)windowStart) + return windowEnd; + + newRun -= *size; + + if (newRun <= (char*)*array) + return newRun; + + if (mctx->compare(newRun - *size, newRun, mctx->compareContext) > 0) { + break; + } + } + + while (true) { + assert(nextWindowSize > 0); + assert(nextWindowSize % *size == 0); + + char* nextWindow = windowStart; + if (newRun > (char*)windowStart + nextWindowSize) { + // This avoids call-preserving "nextWindowSize" (reduces code size). + nextWindow += (size_t)(newRun - ((char*)windowStart + nextWindowSize)); + assert(nextWindow == newRun - nextWindowSize); + assert(nextWindow > (char*)windowStart); + } + + char* lastRun = newRun; + size_t lastRunSize = (size_t)((char*)windowEnd - lastRun); + newRun = mergeSortSubarray(mctx, lastRunSize, nextWindow, lastRun); + assert(newRun <= lastRun); + + if (newRun >= lastRun) { + if (newRun <= (char*)windowStart + nextWindowSize && windowStart > *array) + return windowEnd; + break; + } + + mergeRuns(mctx, newRun, lastRun, windowEnd); + + if (newRun <= (char*)windowStart + lastRunSize) { + break; + } + } + return newRun; +} + +static void mergeSort(void* array, size_t len, size_t size, Object_Compare compare, void* context) { + if (!size) + return; + + assert(len <= SIZE_MAX / size); + + MergeSortContext mctx = { + .elementSize = size, + .compare = compare, + .compareContext = context, + .array = array + }; + (void)mergeSortSubarray(&mctx, len * size, array, (char*)array + len * size); +} + +void Sort_sort(void* array, size_t len, size_t size, Object_Compare compare, void* context) { + mergeSort(array, len, size, compare, context); +} diff --git a/generic/Sort.h b/generic/Sort.h new file mode 100644 index 000000000..d096783ca --- /dev/null +++ b/generic/Sort.h @@ -0,0 +1,17 @@ +#ifndef HEADER_Sort +#define HEADER_Sort +/* +htop - generic/Sort.h +(C) 2025 htop dev team +Released under the GNU GPLv2+, see the COPYING file +in the source distribution for its full text. +*/ + +#include + +#include "Object.h" + + +void Sort_sort(void* array, size_t len, size_t size, Object_Compare compare, void* context); + +#endif diff --git a/pcp/Instance.c b/pcp/Instance.c index 39451fa10..6ca9ab4ed 100644 --- a/pcp/Instance.c +++ b/pcp/Instance.c @@ -138,7 +138,8 @@ static int Instance_compareByKey(const Row* v1, const Row* v2, int key) { return 0; } -static int Instance_compare(const void* v1, const void* v2) { +static int Instance_compare(const void* v1, const void* v2, void* context) { + (void)context; const Instance* i1 = (const Instance*)v1; const Instance* i2 = (const Instance*)v2; const ScreenSettings* ss = i1->super.host->settings->ss;