Skip to content

Commit 25b94fc

Browse files
authored
perf: Refactored sorting to use optimized sort algorithms (#2161)
Refactored code to use optimized sort algorithms. Signed-off-by: dhoard <doug.hoard@gmail.com>
1 parent 3fc7b7b commit 25b94fc

8 files changed

Lines changed: 1073 additions & 78 deletions

File tree

prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/ClassicHistogramBuckets.java

Lines changed: 162 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -110,25 +110,16 @@ private static void sortAndValidate(double[] upperBounds, long[] counts) {
110110
validate(upperBounds, counts);
111111
}
112112

113+
/**
114+
* Sorts upperBounds and counts in place using introspective quicksort.
115+
*
116+
* <p>Algorithm: 3-way quicksort with insertion sort for tiny partitions and heapsort fallback at
117+
* the recursion depth limit. Parallel arrays are swapped in lockstep.
118+
*
119+
* <p>Complexity: O(n log n) average and worst case.
120+
*/
113121
private static void sort(double[] upperBounds, long[] counts) {
114-
// Bubblesort. Should be efficient here as in most cases upperBounds is already sorted.
115-
int n = upperBounds.length;
116-
for (int i = 0; i < n - 1; i++) {
117-
for (int j = 0; j < n - i - 1; j++) {
118-
if (upperBounds[j] > upperBounds[j + 1]) {
119-
swap(j, j + 1, upperBounds, counts);
120-
}
121-
}
122-
}
123-
}
124-
125-
private static void swap(int i, int j, double[] upperBounds, long[] counts) {
126-
double tmpDouble = upperBounds[j];
127-
upperBounds[j] = upperBounds[i];
128-
upperBounds[i] = tmpDouble;
129-
long tmpLong = counts[j];
130-
counts[j] = counts[i];
131-
counts[i] = tmpLong;
122+
DoubleArraySorter.sort(upperBounds, counts);
132123
}
133124

134125
private static void validate(double[] upperBounds, long[] counts) {
@@ -224,4 +215,157 @@ public ClassicHistogramBuckets build() {
224215
return ClassicHistogramBuckets.of(upperBounds, counts);
225216
}
226217
}
218+
219+
/**
220+
* In-place introsort for {@code upperBounds} and parallel {@code counts}.
221+
*
222+
* <p>Uses 3-way quicksort partitioning for large ranges, insertion sort for tiny ranges, and a
223+
* heapsort fallback at the recursion-depth limit to guarantee O(n log n) worst-case complexity.
224+
*/
225+
private static final class DoubleArraySorter {
226+
227+
private static final int INSERTION_SORT_THRESHOLD = 24;
228+
229+
private static void sort(double[] upperBounds, long[] counts) {
230+
int right = upperBounds.length - 1;
231+
if (right <= 0) {
232+
return;
233+
}
234+
introSort(upperBounds, counts, 0, right, depthLimit(upperBounds.length));
235+
}
236+
237+
private static void introSort(
238+
double[] upperBounds, long[] counts, int left, int right, int depthLimit) {
239+
while (left < right) {
240+
if (right - left + 1 <= INSERTION_SORT_THRESHOLD) {
241+
insertionSort(upperBounds, counts, left, right);
242+
return;
243+
}
244+
if (depthLimit == 0) {
245+
heapSort(upperBounds, counts, left, right);
246+
return;
247+
}
248+
depthLimit--;
249+
250+
int mid = left + ((right - left) >>> 1);
251+
int pivotIndex = medianOf3(upperBounds, left, mid, right);
252+
double pivot = upperBounds[pivotIndex];
253+
254+
int lt = left;
255+
int i = left;
256+
int gt = right;
257+
while (i <= gt) {
258+
int cmp = compare(upperBounds[i], pivot);
259+
if (cmp < 0) {
260+
swap(i, lt, upperBounds, counts);
261+
i++;
262+
lt++;
263+
} else if (cmp > 0) {
264+
swap(i, gt, upperBounds, counts);
265+
gt--;
266+
} else {
267+
i++;
268+
}
269+
}
270+
271+
if (lt - left < right - gt) {
272+
introSort(upperBounds, counts, left, lt - 1, depthLimit);
273+
left = gt + 1;
274+
} else {
275+
introSort(upperBounds, counts, gt + 1, right, depthLimit);
276+
right = lt - 1;
277+
}
278+
}
279+
}
280+
281+
private static void insertionSort(double[] upperBounds, long[] counts, int left, int right) {
282+
for (int i = left + 1; i <= right; i++) {
283+
double upperBound = upperBounds[i];
284+
long count = counts[i];
285+
int j = i - 1;
286+
while (j >= left && compare(upperBounds[j], upperBound) > 0) {
287+
upperBounds[j + 1] = upperBounds[j];
288+
counts[j + 1] = counts[j];
289+
j--;
290+
}
291+
upperBounds[j + 1] = upperBound;
292+
counts[j + 1] = count;
293+
}
294+
}
295+
296+
private static void heapSort(double[] upperBounds, long[] counts, int left, int right) {
297+
int size = right - left + 1;
298+
for (int i = (size >>> 1) - 1; i >= 0; i--) {
299+
siftDown(upperBounds, counts, left, i, size);
300+
}
301+
for (int end = size - 1; end > 0; end--) {
302+
swap(left, left + end, upperBounds, counts);
303+
siftDown(upperBounds, counts, left, 0, end);
304+
}
305+
}
306+
307+
private static void siftDown(
308+
double[] upperBounds, long[] counts, int base, int root, int size) {
309+
while (true) {
310+
int child = (root << 1) + 1;
311+
if (child >= size) {
312+
return;
313+
}
314+
int rightChild = child + 1;
315+
if (rightChild < size
316+
&& compare(upperBounds[base + child], upperBounds[base + rightChild]) < 0) {
317+
child = rightChild;
318+
}
319+
if (compare(upperBounds[base + root], upperBounds[base + child]) >= 0) {
320+
return;
321+
}
322+
swap(base + root, base + child, upperBounds, counts);
323+
root = child;
324+
}
325+
}
326+
327+
private static int depthLimit(int length) {
328+
int result = 0;
329+
while (length > 1) {
330+
result++;
331+
length >>>= 1;
332+
}
333+
return result << 1;
334+
}
335+
336+
private static int medianOf3(double[] upperBounds, int i, int j, int k) {
337+
if (compare(upperBounds[i], upperBounds[j]) > 0) {
338+
int tmp = i;
339+
i = j;
340+
j = tmp;
341+
}
342+
if (compare(upperBounds[j], upperBounds[k]) > 0) {
343+
int tmp = j;
344+
j = k;
345+
k = tmp;
346+
}
347+
if (compare(upperBounds[i], upperBounds[j]) > 0) {
348+
int tmp = i;
349+
i = j;
350+
j = tmp;
351+
}
352+
return j;
353+
}
354+
355+
private static int compare(double a, double b) {
356+
return Double.compare(a, b);
357+
}
358+
359+
private static void swap(int i, int j, double[] upperBounds, long[] counts) {
360+
if (i == j) {
361+
return;
362+
}
363+
double upperBound = upperBounds[i];
364+
upperBounds[i] = upperBounds[j];
365+
upperBounds[j] = upperBound;
366+
long count = counts[i];
367+
counts[i] = counts[j];
368+
counts[j] = count;
369+
}
370+
}
227371
}

0 commit comments

Comments
 (0)