/src/server/mysys/mf_qsort.c
Line | Count | Source (jump to first uncovered line) |
1 | | /* Copyright (c) 2000-2002, 2007 MySQL AB |
2 | | Use is subject to license terms |
3 | | |
4 | | This program is free software; you can redistribute it and/or modify |
5 | | it under the terms of the GNU General Public License as published by |
6 | | the Free Software Foundation; version 2 of the License. |
7 | | |
8 | | This program is distributed in the hope that it will be useful, |
9 | | but WITHOUT ANY WARRANTY; without even the implied warranty of |
10 | | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
11 | | GNU General Public License for more details. |
12 | | |
13 | | You should have received a copy of the GNU General Public License |
14 | | along with this program; if not, write to the Free Software |
15 | | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335 USA */ |
16 | | |
17 | | /* |
18 | | qsort implementation optimized for comparison of pointers |
19 | | Inspired by the qsort implementations by Douglas C. Schmidt, |
20 | | and Bentley & McIlroy's "Engineering a Sort Function". |
21 | | */ |
22 | | |
23 | | |
24 | | #include "mysys_priv.h" |
25 | | #ifndef SCO |
26 | | #include <m_string.h> |
27 | | #endif |
28 | | |
29 | | /* We need to use qsort with 2 different compare functions */ |
30 | | #ifdef QSORT_EXTRA_CMP_ARGUMENT |
31 | | #define CMP(A,B) ((*cmp)(cmp_argument,(A),(B))) |
32 | | #else |
33 | 0 | #define CMP(A,B) ((*cmp)((A),(B))) |
34 | | #endif |
35 | | |
36 | 0 | #define SWAP(A, B, size,swap_ptrs) \ |
37 | 0 | do { \ |
38 | 0 | if (swap_ptrs) \ |
39 | 0 | { \ |
40 | 0 | reg1 char **a = (char**) (A), **b = (char**) (B); \ |
41 | 0 | char *tmp = *a; *a++ = *b; *b++ = tmp; \ |
42 | 0 | } \ |
43 | 0 | else \ |
44 | 0 | { \ |
45 | 0 | reg1 char *a = (A), *b = (B); \ |
46 | 0 | reg3 char *end= a+size; \ |
47 | 0 | do \ |
48 | 0 | { \ |
49 | 0 | char tmp = *a; *a++ = *b; *b++ = tmp; \ |
50 | 0 | } while (a < end); \ |
51 | 0 | } \ |
52 | 0 | } while (0) |
53 | | |
54 | | /* Put the median in the middle argument */ |
55 | 0 | #define MEDIAN(low, mid, high) \ |
56 | 0 | { \ |
57 | 0 | if (CMP(high,low) < 0) \ |
58 | 0 | SWAP(high, low, size, ptr_cmp); \ |
59 | 0 | if (CMP(mid, low) < 0) \ |
60 | 0 | SWAP(mid, low, size, ptr_cmp); \ |
61 | 0 | else if (CMP(high, mid) < 0) \ |
62 | 0 | SWAP(mid, high, size, ptr_cmp); \ |
63 | 0 | } |
64 | | |
65 | | /* The following node is used to store ranges to avoid recursive calls */ |
66 | | |
67 | | typedef struct st_stack |
68 | | { |
69 | | char *low,*high; |
70 | | } stack_node; |
71 | | |
72 | 0 | #define PUSH(LOW,HIGH) {stack_ptr->low = LOW; stack_ptr++->high = HIGH;} |
73 | 0 | #define POP(LOW,HIGH) {LOW = (--stack_ptr)->low; HIGH = stack_ptr->high;} |
74 | | |
75 | | /* The following stack size is enough for ulong ~0 elements */ |
76 | | #define STACK_SIZE (8 * sizeof(unsigned long int)) |
77 | 0 | #define THRESHOLD_FOR_INSERT_SORT 10 |
78 | | #if defined(QSORT_TYPE_IS_VOID) |
79 | 0 | #define SORT_RETURN return |
80 | | #else |
81 | | #define SORT_RETURN return 0 |
82 | | #endif |
83 | | |
84 | | /**************************************************************************** |
85 | | ** 'standard' quicksort with the following extensions: |
86 | | ** |
87 | | ** Can be compiled with the qsort2_cmp compare function |
88 | | ** Store ranges on stack to avoid recursion |
89 | | ** Use insert sort on small ranges |
90 | | ** Optimize for sorting of pointers (used often by MySQL) |
91 | | ** Use median comparison to find partition element |
92 | | *****************************************************************************/ |
93 | | |
94 | | #ifdef QSORT_EXTRA_CMP_ARGUMENT |
95 | | qsort_t my_qsort2(void *base_ptr, size_t count, size_t size, qsort2_cmp cmp, |
96 | | void *cmp_argument) |
97 | | #else |
98 | | qsort_t my_qsort(void *base_ptr, size_t count, size_t size, qsort_cmp cmp) |
99 | | #endif |
100 | 0 | { |
101 | 0 | char *low, *high, *pivot; |
102 | 0 | stack_node stack[STACK_SIZE], *stack_ptr; |
103 | 0 | my_bool ptr_cmp; |
104 | | /* Handle the simple case first */ |
105 | | /* This will also make the rest of the code simpler */ |
106 | 0 | if (count <= 1) |
107 | 0 | SORT_RETURN; |
108 | | |
109 | 0 | low = (char*) base_ptr; |
110 | 0 | high = low+ size * (count - 1); |
111 | 0 | stack_ptr = stack + 1; |
112 | | #ifdef HAVE_valgrind |
113 | | /* The first element in the stack will be accessed for the last POP */ |
114 | | stack[0].low=stack[0].high=0; |
115 | | #endif |
116 | 0 | pivot = (char *) my_alloca((int) size); |
117 | 0 | ptr_cmp= size == sizeof(char*) && (intptr_t)low % sizeof(char*) == 0; |
118 | | |
119 | | /* The following loop sorts elements between high and low */ |
120 | 0 | do |
121 | 0 | { |
122 | 0 | char *low_ptr, *high_ptr, *mid; |
123 | |
|
124 | 0 | count=((size_t) (high - low) / size)+1; |
125 | | /* If count is small, then an insert sort is faster than qsort */ |
126 | 0 | if (count < THRESHOLD_FOR_INSERT_SORT) |
127 | 0 | { |
128 | 0 | for (low_ptr = low + size; low_ptr <= high; low_ptr += size) |
129 | 0 | { |
130 | 0 | char *ptr; |
131 | 0 | for (ptr = low_ptr; ptr > low && CMP(ptr - size, ptr) > 0; |
132 | 0 | ptr -= size) |
133 | 0 | SWAP(ptr, ptr - size, size, ptr_cmp); |
134 | 0 | } |
135 | 0 | POP(low, high); |
136 | 0 | continue; |
137 | 0 | } |
138 | | |
139 | | /* Try to find a good middle element */ |
140 | 0 | mid= low + size * (count >> 1); |
141 | 0 | if (count > 40) /* Must be bigger than 24 */ |
142 | 0 | { |
143 | 0 | size_t step = size* (count / 8); |
144 | 0 | MEDIAN(low, low + step, low+step*2); |
145 | 0 | MEDIAN(mid - step, mid, mid+step); |
146 | 0 | MEDIAN(high - 2 * step, high-step, high); |
147 | | /* Put best median in 'mid' */ |
148 | 0 | MEDIAN(low+step, mid, high-step); |
149 | 0 | low_ptr = low; |
150 | 0 | high_ptr = high; |
151 | 0 | } |
152 | 0 | else |
153 | 0 | { |
154 | 0 | MEDIAN(low, mid, high); |
155 | | /* The low and high argument are already in sorted against 'pivot' */ |
156 | 0 | low_ptr = low + size; |
157 | 0 | high_ptr = high - size; |
158 | 0 | } |
159 | 0 | memcpy(pivot, mid, size); |
160 | |
|
161 | 0 | do |
162 | 0 | { |
163 | 0 | while (CMP(low_ptr, pivot) < 0) |
164 | 0 | low_ptr += size; |
165 | 0 | while (CMP(pivot, high_ptr) < 0) |
166 | 0 | high_ptr -= size; |
167 | |
|
168 | 0 | if (low_ptr < high_ptr) |
169 | 0 | { |
170 | 0 | SWAP(low_ptr, high_ptr, size, ptr_cmp); |
171 | 0 | low_ptr += size; |
172 | 0 | high_ptr -= size; |
173 | 0 | } |
174 | 0 | else |
175 | 0 | { |
176 | 0 | if (low_ptr == high_ptr) |
177 | 0 | { |
178 | 0 | low_ptr += size; |
179 | 0 | high_ptr -= size; |
180 | 0 | } |
181 | 0 | break; |
182 | 0 | } |
183 | 0 | } |
184 | 0 | while (low_ptr <= high_ptr); |
185 | | |
186 | | /* |
187 | | Prepare for next iteration. |
188 | | Skip partitions of size 1 as these doesn't have to be sorted |
189 | | Push the larger partition and sort the smaller one first. |
190 | | This ensures that the stack is keept small. |
191 | | */ |
192 | | |
193 | 0 | if ((int) (high_ptr - low) <= 0) |
194 | 0 | { |
195 | 0 | if ((int) (high - low_ptr) <= 0) |
196 | 0 | { |
197 | 0 | POP(low, high); /* Nothing more to sort */ |
198 | 0 | } |
199 | 0 | else |
200 | 0 | low = low_ptr; /* Ignore small left part. */ |
201 | 0 | } |
202 | 0 | else if ((int) (high - low_ptr) <= 0) |
203 | 0 | high = high_ptr; /* Ignore small right part. */ |
204 | 0 | else if ((high_ptr - low) > (high - low_ptr)) |
205 | 0 | { |
206 | 0 | PUSH(low, high_ptr); /* Push larger left part */ |
207 | 0 | low = low_ptr; |
208 | 0 | } |
209 | 0 | else |
210 | 0 | { |
211 | 0 | PUSH(low_ptr, high); /* Push larger right part */ |
212 | 0 | high = high_ptr; |
213 | 0 | } |
214 | 0 | } while (stack_ptr > stack); |
215 | 0 | my_afree(pivot); |
216 | 0 | SORT_RETURN; |
217 | 0 | } |