New Vector_sort function, replacing insertion and quick sort#1798
New Vector_sort function, replacing insertion and quick sort#1798Explorer09 wants to merge 2 commits into
Conversation
0ec958a to
74c2a2d
Compare
bbd8ecd to
aba90b7
Compare
9b974af to
f29067d
Compare
|
Has any performance comparison been done that is sufficiently compelling that we'd want to keep all this extra code, instead of just using qsort? Thanks. |
|
@natoscott The Note my current implementation is not fully async-signal-safe though. The swap operation is not atomic, and can ruin the data when two sort function calls contest on the same data. It's expected that the sort function, when called in asynchronous contexts, operates on different data or the caller must guard the data with a mutex. |
|
Sort stability is about what happens when two items compare equally (which can happen with/without multiple sort keys, so I don't follow your argument there). In htop, we always have the PID we can use as a tie-breaker to guarantee stability. Can you explain how this is insufficient? Regarding threads and the extra parameter, htop is single threaded and is very likely to remain so. So again this is not a valid technical argument against the use of qsort here. qsort is highly optimised and widely reviewed. Any custom home-grown sorting code we have in htop simply isn't, to anywhere near the same degree, and appears to be unnecessary code bloat. So unless there is a valid technical reason otherwise (and none have been put forward so far, AFAICT), I think we should switch to qsort and delete all this code. |
|
@natoscott I assume you have read #1784 and #1785 for the discussion I had with @BenBE before. Basically I would vote for using qsort() (#1784) if there isn't a stability requirement. I suggested that the old And no, I don't think tiebreaking by PID is enough. htop 's process table can be sorted with multiple keys, and there will be a need to sort the table multiple times by the user without explicitly specifying the second sort key. There can be also tables where there isn't an ID field for tiebreaking. And there's a technical thing: The Therefore the most future-proof way is to implement a stable sorting function by ourselves. Note that I've developed this algorithm for about half a month, and it would be sad for me if it's ditched finally. |
|
I have read those, yes - there's little/no justification there for custom sort implementations. It seemed like there was consensus that qsort would be fine based on Hishams reasons for the original implementation, but then it somehow became an exercise in writing new code with insufficient justification IMO. | There can be also tables where there isn't an ID field for tiebreaking. What tables are you referring to here? I don't see them in the code today (something else I've missed?) - if they exist today, do these things require sort stability? Thanks. The main (only?) data we need to sort is the process section (lower part) of the htop display, of course, where we always have a unique identifier (PID) for every entry. The PID provides stability with any number of secondary sort keys, its not clear what problem you are talking about there. And the user never needs to be involved or know it is being used, again that's a tangent that just doesn't make any sense to me. Just do all the current sort comparisons, including with multiple keys, and only use the PID as a final, stable, unique differentiator when everything else compares as equal. Sure, some code refactoring may be required to ensure the PID is available within comparison routines, but its a solvable problem. I understand the limitations of the qsort API (these are solvable for htop though). I also understand the problems of creating custom sort implementations. Its relatively straight-forward to sort things, its more difficult to perform well in general and particularly with very large process counts. | Therefore the most future-proof way [...] It sounds like you are designing for problems htop does not currently have. Can you explain specifically what future features you foresee that cannot use qsort with a stable identifier tie-breaker? Thanks. Either way, it will be interesting to see performance numbers comparing performance at both very large and small numbers of processes. If a different algorithm significantly outperforms qsort for sorting processes in htop, we should use it. |
The current process compare functions always sort PID in the ascending order for tiebreaking. No way to make it into descending order. Sorry I just don't like this. (If we were sorting a table in a SQL database, a fallback key for tiebreaking is reasonable. But htop is not a DB.)
Global variables, I know. When I proposed that @BenBE doesn't seem to like it. So it's not my decision on this part. qsort() uses introsort in glibc, and smoothsort in musl. So, on the question of how the algorithm performs well, I think we're judging quite subjectively. And it would be better if we have some benchmarks. (By the way, the old PR is there if you want to test the use of qsort() in htop.) |
Just a minor note. Sorting algorithms' performance can be compared with multiple kinds of data (use cases), and so it won't be a single metric that could decide which algorithm would win. What I am saying is there can be uniformly random data as well as partially sorted data that need to be sorted. And for an algorithm like heapsort (as an example) it would work best with random data but relatively poor with data that's half sorted. Quicksort with a naïve pivot selection can easily make it the worst case (O(n²)) for data that's already sorted or reverse-sorted, you know. I'm not criticising either algorithm or qsort() API, just a note that the qsort() default algorithm is not adaptive in most libc implementations, thus not as "universally useful" as you think. And there is no "perfect" sorting algorithm. Each algorithm comes with a trade-off. Even my algorithm does. If you are looking for block sort family that looks "perfect" enough in the stats, I did realise there is a tradeoff (adaptive sort i.e. O(n) best case time complexity or O(1) space complexity, pick one). So really no algorithm is perfect. |
|
@Explorer09 I did a little more digging and have found all supported htop platforms have a variant of qsort_r - IIRC you mentioned earlier that its missing somewhere? (even modern Solaris has it, and Windows has qsort_s). Which platforms were you thinking do not have it? There are differences in the calling conventions between platforms, but they are trivially resolved via wrapper. | And there is no "perfect" sorting algorithm. From a htop POV, when we can delete substantial code it's a good win - so qsort[_r] would be perfect in that sense IMO. I honestly cannot fathom why anyone would be implementing custom sorting code for a system performance tool, that just blows my mind. If we did need qsort_r for some unusual / new platform, there are plenty of existing, tested, robust implementations to pick from that are license-compatible with htop - choose one, put it below generic/ and add some configure magic. Done. |
The catch is how to detect the calling conventions. // Older BSD
void qsort_r(void*, size_t, size_t, void*, int(*)(void*, const void*, const void*));
// POSIX, GNU
void qsort_r(void*, size_t, size_t, int(*)(const void*, const void*, void*), void*);Arguments 4 and 5 are both pointers, so only the const qualifier can tell which argument does what. At this point I am no longer interested in writing a |
|
| The catch is how to detect the calling conventions. Each platform will know the conventions of its own qsort_r, and a xxx/Platform.h wrapper can map that to whichever convention is chosen for use throughout htop. The only requirement for configure is to detect whether there is a qsort_r at all ... which AIUI you said some platforms are missing? If none are missing it, then no configure changes are needed. If it is missing somewhere, configure would need to enable a generic/qsort.c implementation (with contents from somewhere like https://github.com/freebsd/freebsd-src/blob/main/lib/libc/stdlib/qsort.c for example). |
I just heard on DistroWatch that htop is being ported to Redox OS! And no, I don't know them. It's just something I heard unintentionally. And FYI, Redox OS's libc didn't have qsort_r at the moment, although I believe it's trivial for them to implement one.
FreeBSD provides both versions of the |
f29067d to
7198071
Compare
c068947 to
0c6d188
Compare
d72dff0 to
81e7498
Compare
ccb91f0 to
f54f7f9
Compare
d2d599d to
9a9d5ab
Compare
759627f to
ca1dc8c
Compare
037a402 to
eabb44b
Compare
6762fa4 to
9b452f1
Compare
ℹ️ Recent review info⚙️ Run configurationConfiguration used: Repository UI Review profile: ASSERTIVE Plan: Pro Plus Run ID: 📒 Files selected for processing (18)
📝 WalkthroughSummaryThis PR introduces a new stable, in-place merge sort API ( Core ChangesNew sorting subsystem: 184 lines added ( API redesign: Extends Systematic refactor: ~20 comparison function signatures updated consistently— Call-site updates: Vector.c, Table.c, and five screen implementations (Action, EnvScreen, OpenFilesScreen, ProcessLocksScreen) replaced with Implementation AssessmentStrengths:
Concerns:
Commit StructureChanges are logically split by file with focused modifications. However, the approach of extending WalkthroughThis PR refactors htop's sorting infrastructure to use a unified, context-aware architecture. The core change replaces scattered sorting implementations (Vector_insertionSort, Vector_quickSortCustomCompare) with a new generic stable merge sort (Sort_sort) and updates all comparison callbacks to accept an optional void* context parameter. The Object_Compare typedef is extended to include context, and all comparators (Row, Process, ListItem, Instance) are updated to match the new signature. Vector_sort becomes the single entry point delegating to Sort_sort. All call sites—table rebuild, display list updates, and screen scans—are updated to use Vector_sort with the appropriate context parameter. Poem
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 4
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
Vector.c (1)
292-300: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick winThread comparator context through
Vector_indexOf()as well.Line 300 still hard-codes
NULL, so a comparator that works withVector_sort()cannot be reused for lookups once it needs the new third argument. That leaves theObject_Comparecontract inconsistent insideVector.Proposed fix
-int Vector_indexOf(const Vector* this, const void* search_, Object_Compare compare) { +int Vector_indexOf(const Vector* this, const void* search_, Object_Compare compare, void* context) { const Object* search = search_; assert(Object_isA(search, this->type)); assert(compare); assert(Vector_isConsistent(this)); for (int i = 0; i < this->items; i++) { const Object* o = this->array[i]; assert(o); - if (compare(search, o, NULL) == 0) { + if (compare(search, o, context) == 0) { return i; } } return -1; }Update the declaration in
Vector.hto match.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 66593feb-2e96-493e-9807-a14875be6fec
📒 Files selected for processing (18)
Action.cEnvScreen.cListItem.cListItem.hMakefile.amObject.hOpenFilesScreen.cProcess.cProcess.hProcessLocksScreen.cRow.cRow.hTable.cVector.cVector.hgeneric/Sort.cgeneric/Sort.hpcp/Instance.c
| void* array; | ||
| } MergeSortContext; | ||
|
|
||
| static void swapByte(char* p1, char* p2) { |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# List static function definitions in Sort.c to verify naming style.
rg -nP '^\s*static\s+[^()]+\s+([A-Za-z_][A-Za-z0-9_]*)\s*\(' generic/Sort.cRepository: htop-dev/htop
Length of output: 419
🏁 Script executed:
#!/bin/bash
# Check if Sort.h exists and examine naming of public functions
ls -la generic/Sort.h 2>/dev/null || echo "No Sort.h found"
# Look at a few other .c files to see how static functions are named
echo "=== Static functions in other .c files ==="
rg -n '^\s*static\s+[^()]+\s+([A-Za-z_][A-Za-z0-9_]*)\s*\(' --max-count=3 | head -20Repository: htop-dev/htop
Length of output: 1791
🏁 Script executed:
#!/bin/bash
# Examine Sort.h to see the public API naming pattern
cat -n generic/Sort.h
# Also check if there are any Sort_ prefix functions in Sort.c
echo "=== Functions with Sort_ prefix in Sort.c ==="
rg -n 'Sort_' generic/Sort.c | head -20Repository: htop-dev/htop
Length of output: 654
🏁 Script executed:
#!/bin/bash
# Check if any of these functions are referenced outside Sort.c
echo "=== External references to swapByte ==="
rg -n 'swapByte' --type c --type h | grep -v 'generic/Sort.c'
echo "=== External references to mergeRuns ==="
rg -n 'mergeRuns' --type c --type h | grep -v 'generic/Sort.c'
echo "=== External references to mergeSortSubarray ==="
rg -n 'mergeSortSubarray' --type c --type h | grep -v 'generic/Sort.c'
echo "=== External references to mergeSort ==="
rg -n 'mergeSort' --type c --type h | grep -v 'generic/Sort.c'Repository: htop-dev/htop
Length of output: 228
Rename static helper functions to follow Sort_functionName() convention for consistency.
The static functions swapByte, mergeRuns, mergeSortSubarray, and mergeSort do not align with the module naming convention used in the public API (Sort_sort). These should be renamed to Sort_swapByte, Sort_mergeRuns, Sort_mergeSortSubarray, and Sort_mergeSort respectively. As these are module-private functions with no external references, the refactoring is low-effort and improves internal consistency per the coding guidelines.
Also applies to: lines 31, 90, 150
| 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); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Inspect mergeSort guards and arithmetic site.
rg -nP 'static void mergeSort\(' generic/Sort.c -A35
rg -nP 'if\s*\(\s*len\s*<\s*2' generic/Sort.c || true
rg -nP 'if\s*\(\s*len\s*>\s*SIZE_MAX\s*/\s*size\s*\)' generic/Sort.c || true
rg -nP 'len\s*\*\s*size' generic/Sort.cRepository: htop-dev/htop
Length of output: 752
Add runtime guards for overflow and trivial lengths in mergeSort.
Line 154 relies on assert() for overflow protection, which is stripped in release builds (-DNDEBUG). This leaves the len * size multiplication on line 162 unguarded. Additionally, the function should short-circuit for len < 2 to avoid unnecessary pointer arithmetic and recursion.
Proposed fix
static void mergeSort(void* array, size_t len, size_t size, Object_Compare compare, void* context) {
- if (!size)
+ if (!size || len < 2)
return;
- assert(len <= SIZE_MAX / size);
+ if (len > SIZE_MAX / size) {
+ assert(len <= SIZE_MAX / size);
+ return;
+ }
MergeSortContext mctx = {
.elementSize = size,
.compare = compare,
.compareContext = context,
.array = array
};
(void)mergeSortSubarray(&mctx, len * size, array, (char*)array + len * size);
}There was a problem hiding this comment.
Actionable comments posted: 1
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 00622385-c1ba-421e-9a74-f4083a5281b2
📒 Files selected for processing (18)
Action.cEnvScreen.cListItem.cListItem.hMakefile.amObject.hOpenFilesScreen.cProcess.cProcess.hProcessLocksScreen.cRow.cRow.hTable.cVector.cVector.hgeneric/Sort.cgeneric/Sort.hpcp/Instance.c
| static int compareRowByKnownParentThenNatural(const void* v1, const void* v2, void* context) { | ||
| return Row_compareByParent((const Row*) v1, (const Row*) v2, context); |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win
Rename helper to match module naming convention.
compareRowByKnownParentThenNatural should use the Table_ module prefix to align with project function naming rules.
Proposed refactor
-static int compareRowByKnownParentThenNatural(const void* v1, const void* v2, void* context) {
+static int Table_compareRowByKnownParentThenNatural(const void* v1, const void* v2, void* context) {
return Row_compareByParent((const Row*) v1, (const Row*) v2, context);
}
@@
- Vector_sort(this->rows, compareRowByKnownParentThenNatural, this);
+ Vector_sort(this->rows, Table_compareRowByKnownParentThenNatural, this);As per coding guidelines, "Use ModuleName_functionName() naming convention for C functions (e.g. Process_compare())."
Also applies to: 185-185
|
@coderabbitat Please note the context and requirements given in #1785. |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: Length of output: 94 🏁 Script executed: Length of output: 17159
To summarize how
The primary deviation from the interface you outlined in Given that
This would let Explorer09 (and others) know whether type-naming alignment is needed before further review is productive. |
|
@coderabbitai The benchmark branch up to d3cd557 is ready as a baseline (basically uses the existing code) but with the new interface. To minimize effects caused by function signatures, using Object_SortFn is highly encouraged. This makes comparing the results much easier to compare. If not using that signature, a good reason for diverting from it should be given though. |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: Length of output: 3393 🏁 Script executed: Length of output: 6790
Having reviewed
// Vector.h
typedef int (*Vector_SortFn)(const void *a, const void *b, void *ctx);
void Vector_sort(Vector* this, Vector_SortFn cmp, void* ctx);
// Object_Compare stays as the original 2-arg form
PR // Object.h — Object_Compare itself extended to 3 args
typedef int (*Object_Compare)(const void*, const void*, void*);
void Vector_sort(Vector* this, Object_Compare compare, void* context);The 3-arg signature is the same, but it is expressed by mutating What this means for To align with the benchmark baseline, Explorer09 needs to decide:
From a benchmark-comparison standpoint, option 1 is cleaner: the only variable being tested is the sorting algorithm, not the comparator-calling convention throughout the type hierarchy. ✏️ Learnings added
|
Update the prototypes of all Object_Compare functions and Vector_*Sort() functions to accept the third argument. The third argument allows passing in extra information or states that could be useful in sorting. The definition of Object_Compare now matches the prototype of the compare function in libc qsort_r(). (Previously many programmers would use global variables to read or store extra information needed in a compare operation. This made the sort function not reentrant. It was the primary motivation for the qsort_r() function.) Currently all Object_Compare functions in htop would simply the third argument. No functions are using it yet, but this may be changed in the future. The callers of Vector_*Sort() will now by default pass in the reference of the container object (usually named "this") as the third argument to the sort compare functions. Signed-off-by: Kang-Che Sung <explorer09@gmail.com>
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>
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (3)
generic/Sort.c (2)
25-25: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick winRename static helper functions to follow
Sort_functionName()convention.The static functions
swapByte,mergeRuns,mergeSortSubarray, andmergeSortdo not align with theModuleName_functionName()naming convention used for the public API (Sort_sort). Rename them toSort_swapByte,Sort_mergeRuns,Sort_mergeSortSubarray, andSort_mergeSortrespectively for consistency with coding guidelines.Also applies to: 31-31, 90-90, 150-150
Source: Coding guidelines
150-163:⚠️ Potential issue | 🟠 Major | ⚡ Quick winAdd runtime guards for overflow and trivial lengths in
mergeSort.Line 154 relies on
assert()for overflow protection, which is stripped in release builds (-DNDEBUG), leaving thelen * sizemultiplication on line 162 unguarded. Additionally, the function should short-circuit forlen < 2to avoid unnecessary pointer arithmetic and recursion.Proposed fix
static void mergeSort(void* array, size_t len, size_t size, Object_Compare compare, void* context) { - if (!size) + if (!size || len < 2) return; - assert(len <= SIZE_MAX / size); + if (len > SIZE_MAX / size) { + assert(len <= SIZE_MAX / size); + return; + } MergeSortContext mctx = { .elementSize = size, .compare = compare, .compareContext = context, .array = array }; (void)mergeSortSubarray(&mctx, len * size, array, (char*)array + len * size); }Vector.c (1)
112-115:⚠️ Potential issue | 🟠 Major | ⚡ Quick winPreserve the caller's
NULLcomparator context.Line 114 silently replaces
NULLwiththis, preventing callers from passing an explicit null context to comparators that may distinguish between "no context" and "Vector as context." This forcesthisas the context even when the caller intendsNULL.Proposed fix
VectorSortContext vc = { .compare = compare ? compare : this->type->compare, - .compareContext = context ? context : this, + .compareContext = context, };
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 00ef9052-7a56-4619-8f73-8fdaacddfc3e
📒 Files selected for processing (18)
Action.cEnvScreen.cListItem.cListItem.hMakefile.amObject.hOpenFilesScreen.cProcess.cProcess.hProcessLocksScreen.cRow.cRow.hTable.cVector.cVector.hgeneric/Sort.cgeneric/Sort.hpcp/Instance.c
There was a problem hiding this comment.
Caution
Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.
Actionable comments posted: 1
♻️ Duplicate comments (3)
generic/Sort.c (2)
25-25: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick winRename static helper functions to follow
Sort_functionName()convention.The static functions
swapByte,mergeRuns,mergeSortSubarray, andmergeSortdo not align with theModuleName_functionName()naming convention used for the public API (Sort_sort). Rename them toSort_swapByte,Sort_mergeRuns,Sort_mergeSortSubarray, andSort_mergeSortrespectively for consistency with coding guidelines.Also applies to: 31-31, 90-90, 150-150
Source: Coding guidelines
150-163:⚠️ Potential issue | 🟠 Major | ⚡ Quick winAdd runtime guards for overflow and trivial lengths in
mergeSort.Line 154 relies on
assert()for overflow protection, which is stripped in release builds (-DNDEBUG), leaving thelen * sizemultiplication on line 162 unguarded. Additionally, the function should short-circuit forlen < 2to avoid unnecessary pointer arithmetic and recursion.Proposed fix
static void mergeSort(void* array, size_t len, size_t size, Object_Compare compare, void* context) { - if (!size) + if (!size || len < 2) return; - assert(len <= SIZE_MAX / size); + if (len > SIZE_MAX / size) { + assert(len <= SIZE_MAX / size); + return; + } MergeSortContext mctx = { .elementSize = size, .compare = compare, .compareContext = context, .array = array }; (void)mergeSortSubarray(&mctx, len * size, array, (char*)array + len * size); }Vector.c (1)
112-115:⚠️ Potential issue | 🟠 Major | ⚡ Quick winPreserve the caller's
NULLcomparator context.Line 114 silently replaces
NULLwiththis, preventing callers from passing an explicit null context to comparators that may distinguish between "no context" and "Vector as context." This forcesthisas the context even when the caller intendsNULL.Proposed fix
VectorSortContext vc = { .compare = compare ? compare : this->type->compare, - .compareContext = context ? context : this, + .compareContext = context, };
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 00ef9052-7a56-4619-8f73-8fdaacddfc3e
📒 Files selected for processing (18)
Action.cEnvScreen.cListItem.cListItem.hMakefile.amObject.hOpenFilesScreen.cProcess.cProcess.hProcessLocksScreen.cRow.cRow.hTable.cVector.cVector.hgeneric/Sort.cgeneric/Sort.hpcp/Instance.c
🛑 Comments failed to post (1)
Process.c (1)
349-372: 🧹 Nitpick | 🔵 Trivial | ⚖️ Poor tradeoff
Unrelated change mixed into sorting refactor.
This /proc pseudo-path stripping logic modifies command-display behavior (Process_makeCommandStr), not comparator signatures. It does not belong in a PR focused on updating sorting infrastructure. Extract this to a separate commit or PR to maintain clear change boundaries and simplify review.
Introduce the new
Vector_sort()function and obsolete the oldVector_quickSortCustomCompare()andVector_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
Time complexity: O(n) best case, O(n*log(n)*log(n)) average & worst case
@BenBE , I have copied some of the changes from your d3cd557 commit but I didn't base on it. Because there are some data type naming that I didn't like, and so I name my "context" structure in my own way. Also to keep changes small I didn't upgrade the
Object_Comparefunction type to take a new "context" parameter.