/* Copyright (C) 2000, 2005 MySQL AB This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; version 2 of the License. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ /* Code for generell handling of priority Queues. Implemention of queues from "Algoritms in C" by Robert Sedgewick. An optimisation of _downheap suggested in Exercise 7.51 in "Data Structures & Algorithms in C++" by Mark Allen Weiss, Second Edition was implemented by Mikael Ronstrom 2005. Also the O(N) algorithm of queue_fix was implemented. */ #include "mysys_priv.h" #include "mysys_err.h" #include /* Init queue SYNOPSIS init_queue() queue Queue to initialise max_elements Max elements that will be put in queue offset_to_key Offset to key in element stored in queue Used when sending pointers to compare function max_at_top Set to 1 if you want biggest element on top. compare Compare function for elements, takes 3 arguments. first_cmp_arg First argument to compare function NOTES Will allocate max_element pointers for queue array RETURN 0 ok 1 Could not allocate memory */ int init_queue(QUEUE *queue, uint max_elements, uint offset_to_key, pbool max_at_top, int (*compare) (void *, byte *, byte *), void *first_cmp_arg) { DBUG_ENTER("init_queue"); if ((queue->root= (byte **) my_malloc((max_elements+1)*sizeof(void*), MYF(MY_WME))) == 0) DBUG_RETURN(1); queue->elements=0; queue->compare=compare; queue->first_cmp_arg=first_cmp_arg; queue->max_elements=max_elements; queue->offset_to_key=offset_to_key; queue->max_at_top= max_at_top ? (-1 ^ 1) : 0; DBUG_RETURN(0); } /* Init queue, uses init_queue internally for init work but also accepts auto_extent as parameter SYNOPSIS init_queue_ex() queue Queue to initialise max_elements Max elements that will be put in queue offset_to_key Offset to key in element stored in queue Used when sending pointers to compare function max_at_top Set to 1 if you want biggest element on top. compare Compare function for elements, takes 3 arguments. first_cmp_arg First argument to compare function auto_extent When the queue is full and there is insert operation extend the queue. NOTES Will allocate max_element pointers for queue array RETURN 0 ok 1 Could not allocate memory */ int init_queue_ex(QUEUE *queue, uint max_elements, uint offset_to_key, pbool max_at_top, int (*compare) (void *, byte *, byte *), void *first_cmp_arg, uint auto_extent) { int ret; DBUG_ENTER("init_queue_ex"); if ((ret= init_queue(queue, max_elements, offset_to_key, max_at_top, compare, first_cmp_arg))) DBUG_RETURN(ret); queue->auto_extent= auto_extent; DBUG_RETURN(0); } /* Reinitialize queue for other usage SYNOPSIS reinit_queue() queue Queue to initialise max_elements Max elements that will be put in queue offset_to_key Offset to key in element stored in queue Used when sending pointers to compare function max_at_top Set to 1 if you want biggest element on top. compare Compare function for elements, takes 3 arguments. first_cmp_arg First argument to compare function NOTES This will delete all elements from the queue. If you don't want this, use resize_queue() instead. RETURN 0 ok EE_OUTOFMEMORY Wrong max_elements */ int reinit_queue(QUEUE *queue, uint max_elements, uint offset_to_key, pbool max_at_top, int (*compare) (void *, byte *, byte *), void *first_cmp_arg) { DBUG_ENTER("reinit_queue"); queue->elements=0; queue->compare=compare; queue->first_cmp_arg=first_cmp_arg; queue->offset_to_key=offset_to_key; queue->max_at_top= max_at_top ? (-1 ^ 1) : 0; resize_queue(queue, max_elements); DBUG_RETURN(0); } /* Resize queue SYNOPSIS resize_queue() queue Queue max_elements New max size for queue NOTES If you resize queue to be less than the elements you have in it, the extra elements will be deleted RETURN 0 ok 1 Error. In this case the queue is unchanged */ int resize_queue(QUEUE *queue, uint max_elements) { byte **new_root; DBUG_ENTER("resize_queue"); if (queue->max_elements == max_elements) DBUG_RETURN(0); if ((new_root= (byte **) my_realloc((void *)queue->root, (max_elements+1)*sizeof(void*), MYF(MY_WME))) == 0) DBUG_RETURN(1); set_if_smaller(queue->elements, max_elements); queue->max_elements= max_elements; queue->root= new_root; DBUG_RETURN(0); } /* Delete queue SYNOPSIS delete_queue() queue Queue to delete IMPLEMENTATION Just free allocated memory. NOTES Can be called safely multiple times */ void delete_queue(QUEUE *queue) { DBUG_ENTER("delete_queue"); if (queue->root) { my_free((gptr) queue->root,MYF(0)); queue->root=0; } DBUG_VOID_RETURN; } /* Code for insert, search and delete of elements */ void queue_insert(register QUEUE *queue, byte *element) { reg2 uint idx, next; int cmp; DBUG_ASSERT(queue->elements < queue->max_elements); queue->root[0]= element; idx= ++queue->elements; /* max_at_top swaps the comparison if we want to order by desc */ while ((cmp= queue->compare(queue->first_cmp_arg, element + queue->offset_to_key, queue->root[(next= idx >> 1)] + queue->offset_to_key)) && (cmp ^ queue->max_at_top) < 0) { queue->root[idx]= queue->root[next]; idx= next; } queue->root[idx]= element; } /* Does safe insert. If no more space left on the queue resize it. Return codes: 0 - OK 1 - Cannot allocate more memory 2 - auto_extend is 0, the operation would */ int queue_insert_safe(register QUEUE *queue, byte *element) { if (queue->elements == queue->max_elements) { if (!queue->auto_extent) return 2; else if (resize_queue(queue, queue->max_elements + queue->auto_extent)) return 1; } queue_insert(queue, element); return 0; } /* Remove item from queue */ /* Returns pointer to removed element */ byte *queue_remove(register QUEUE *queue, uint idx) { byte *element; DBUG_ASSERT(idx < queue->max_elements); element= queue->root[++idx]; /* Intern index starts from 1 */ queue->root[idx]= queue->root[queue->elements--]; _downheap(queue, idx); return element; } /* Fix when element on top has been replaced */ #ifndef queue_replaced void queue_replaced(QUEUE *queue) { _downheap(queue,1); } #endif #ifndef OLD_VERSION void _downheap(register QUEUE *queue, uint idx) { byte *element; uint elements,half_queue,offset_to_key, next_index; bool first= TRUE; uint start_idx= idx; offset_to_key=queue->offset_to_key; element=queue->root[idx]; half_queue=(elements=queue->elements) >> 1; while (idx <= half_queue) { int cmp; next_index=idx+idx; if (next_index < elements && (queue->compare(queue->first_cmp_arg, queue->root[next_index]+offset_to_key, queue->root[next_index+1]+offset_to_key) ^ queue->max_at_top) > 0) next_index++; if (first && (((cmp=queue->compare(queue->first_cmp_arg, queue->root[next_index]+offset_to_key, element+offset_to_key)) == 0) || ((cmp ^ queue->max_at_top) > 0))) { queue->root[idx]= element; return; } queue->root[idx]=queue->root[next_index]; idx=next_index; first= FALSE; } next_index= idx >> 1; while (next_index > start_idx) { if ((queue->compare(queue->first_cmp_arg, queue->root[next_index]+offset_to_key, element+offset_to_key) ^ queue->max_at_top) < 0) break; queue->root[idx]=queue->root[next_index]; idx=next_index; next_index= idx >> 1; } queue->root[idx]=element; } #else /* The old _downheap version is kept for comparisons with the benchmark suit or new benchmarks anyone wants to run for comparisons. */ /* Fix heap when index have changed */ void _downheap(register QUEUE *queue, uint idx) { byte *element; uint elements,half_queue,next_index,offset_to_key; int cmp; offset_to_key=queue->offset_to_key; element=queue->root[idx]; half_queue=(elements=queue->elements) >> 1; while (idx <= half_queue) { next_index=idx+idx; if (next_index < elements && (queue->compare(queue->first_cmp_arg, queue->root[next_index]+offset_to_key, queue->root[next_index+1]+offset_to_key) ^ queue->max_at_top) > 0) next_index++; if ((cmp=queue->compare(queue->first_cmp_arg, queue->root[next_index]+offset_to_key, element+offset_to_key)) == 0 || (cmp ^ queue->max_at_top) > 0) break; queue->root[idx]=queue->root[next_index]; idx=next_index; } queue->root[idx]=element; } #endif /* Fix heap when every element was changed. */ void queue_fix(QUEUE *queue) { uint i; for (i= queue->elements >> 1; i > 0; i--) _downheap(queue, i); } #ifdef MAIN /* A test program for the priority queue implementation. It can also be used to benchmark changes of the implementation Build by doing the following in the directory mysys make test_priority_queue ./test_priority_queue Written by Mikael Ronström, 2005 */ static uint num_array[1025]; static uint tot_no_parts= 0; static uint tot_no_loops= 0; static uint expected_part= 0; static uint expected_num= 0; static bool max_ind= 0; static bool fix_used= 0; static ulonglong start_time= 0; static bool is_divisible_by(uint num, uint divisor) { uint quotient= num / divisor; if (quotient * divisor == num) return TRUE; return FALSE; } void calculate_next() { uint part= expected_part, num= expected_num; uint no_parts= tot_no_parts; if (max_ind) { do { while (++part <= no_parts) { if (is_divisible_by(num, part) && (num <= ((1 << 21) + part))) { expected_part= part; expected_num= num; return; } } part= 0; } while (--num); } else { do { while (--part > 0) { if (is_divisible_by(num, part)) { expected_part= part; expected_num= num; return; } } part= no_parts + 1; } while (++num); } } void calculate_end_next(uint part) { uint no_parts= tot_no_parts, num; num_array[part]= 0; if (max_ind) { expected_num= 0; for (part= no_parts; part > 0 ; part--) { if (num_array[part]) { num= num_array[part] & 0x3FFFFF; if (num >= expected_num) { expected_num= num; expected_part= part; } } } if (expected_num == 0) expected_part= 0; } else { expected_num= 0xFFFFFFFF; for (part= 1; part <= no_parts; part++) { if (num_array[part]) { num= num_array[part] & 0x3FFFFF; if (num <= expected_num) { expected_num= num; expected_part= part; } } } if (expected_num == 0xFFFFFFFF) expected_part= 0; } return; } static int test_compare(void *null_arg, byte *a, byte *b) { uint a_num= (*(uint*)a) & 0x3FFFFF; uint b_num= (*(uint*)b) & 0x3FFFFF; uint a_part, b_part; if (a_num > b_num) return +1; if (a_num < b_num) return -1; a_part= (*(uint*)a) >> 22; b_part= (*(uint*)b) >> 22; if (a_part < b_part) return +1; if (a_part > b_part) return -1; return 0; } bool check_num(uint num_part) { uint part= num_part >> 22; uint num= num_part & 0x3FFFFF; if (part == expected_part) if (num == expected_num) return FALSE; printf("Expect part %u Expect num 0x%x got part %u num 0x%x max_ind %u fix_used %u \n", expected_part, expected_num, part, num, max_ind, fix_used); return TRUE; } void perform_insert(QUEUE *queue) { uint i= 1, no_parts= tot_no_parts; uint backward_start= 0; expected_part= 1; expected_num= 1; if (max_ind) backward_start= 1 << 21; do { uint num= (i + backward_start); if (max_ind) { while (!is_divisible_by(num, i)) num--; if (max_ind && (num > expected_num || (num == expected_num && i < expected_part))) { expected_num= num; expected_part= i; } } num_array[i]= num + (i << 22); if (fix_used) queue_element(queue, i-1)= (byte*)&num_array[i]; else queue_insert(queue, (byte*)&num_array[i]); } while (++i <= no_parts); if (fix_used) { queue->elements= no_parts; queue_fix(queue); } } bool perform_ins_del(QUEUE *queue, bool max_ind) { uint i= 0, no_loops= tot_no_loops, j= tot_no_parts; do { uint num_part= *(uint*)queue_top(queue); uint part= num_part >> 22; if (check_num(num_part)) return TRUE; if (j++ >= no_loops) { calculate_end_next(part); queue_remove(queue, (uint) 0); } else { calculate_next(); if (max_ind) num_array[part]-= part; else num_array[part]+= part; queue_top(queue)= (byte*)&num_array[part]; queue_replaced(queue); } } while (++i < no_loops); return FALSE; } bool do_test(uint no_parts, uint l_max_ind, bool l_fix_used) { QUEUE queue; bool result; max_ind= l_max_ind; fix_used= l_fix_used; init_queue(&queue, no_parts, 0, max_ind, test_compare, NULL); tot_no_parts= no_parts; tot_no_loops= 1024; perform_insert(&queue); if ((result= perform_ins_del(&queue, max_ind))) delete_queue(&queue); if (result) { printf("Error\n"); return TRUE; } return FALSE; } static void start_measurement() { start_time= my_getsystime(); } static void stop_measurement() { ulonglong stop_time= my_getsystime(); uint time_in_micros; stop_time-= start_time; stop_time/= 10; /* Convert to microseconds */ time_in_micros= (uint)stop_time; printf("Time expired is %u microseconds \n", time_in_micros); } static void benchmark_test() { QUEUE queue_real; QUEUE *queue= &queue_real; uint i, add; fix_used= TRUE; max_ind= FALSE; tot_no_parts= 1024; init_queue(queue, tot_no_parts, 0, max_ind, test_compare, NULL); /* First benchmark whether queue_fix is faster than using queue_insert for sizes of 16 partitions. */ for (tot_no_parts= 2, add=2; tot_no_parts < 128; tot_no_parts+= add, add++) { printf("Start benchmark queue_fix, tot_no_parts= %u \n", tot_no_parts); start_measurement(); for (i= 0; i < 128; i++) { perform_insert(queue); queue_remove_all(queue); } stop_measurement(); fix_used= FALSE; printf("Start benchmark queue_insert\n"); start_measurement(); for (i= 0; i < 128; i++) { perform_insert(queue); queue_remove_all(queue); } stop_measurement(); } /* Now benchmark insertion and deletion of 16400 elements. Used in consecutive runs this shows whether the optimised _downheap is faster than the standard implementation. */ printf("Start benchmarking _downheap \n"); start_measurement(); perform_insert(queue); for (i= 0; i < 65536; i++) { uint num, part; num= *(uint*)queue_top(queue); num+= 16; part= num >> 22; num_array[part]= num; queue_top(queue)= (byte*)&num_array[part]; queue_replaced(queue); } for (i= 0; i < 16; i++) queue_remove(queue, (uint) 0); queue_remove_all(queue); stop_measurement(); } int main() { int i, add= 1; for (i= 1; i < 1024; i+=add, add++) { printf("Start test for priority queue of size %u\n", i); if (do_test(i, 0, 1)) return -1; if (do_test(i, 1, 1)) return -1; if (do_test(i, 0, 0)) return -1; if (do_test(i, 1, 0)) return -1; } benchmark_test(); printf("OK\n"); return 0; } #endif