add Imager's memory debugger
[poe-xs-queue-array.git] / queue.c
1 #include "EXTERN.h"\r
2 #include "perl.h"\r
3 #include "XSUB.h"\r
4 \r
5 #include "queue.h"\r
6 #include "alloc.h"\r
7 \r
8 /*#define DEBUG(x) x*/\r
9 #define DEBUG(x)\r
10 #define DEBUG_ERR(x) x\r
11 /*#define DEBUG_ERR(x)*/\r
12 \r
13 #define PQ_START_SIZE 10\r
14 #define AT_START 0\r
15 #define AT_END 1\r
16 \r
17 pq_id_t queue_seq;\r
18 \r
19 /*\r
20 We store the queue in a similar way to the way perl deals with arrays,\r
21 we keep a block of memory, but the first element may or may not be in use,\r
22 depending on the pattern of usage.\r
23 \r
24 There's 3 value controlling usage of the array:\r
25 \r
26   - alloc - the number of elements allocated in total\r
27   - start - the first element in use in the array\r
28   - end - one past the end of the last element in the array\r
29 \r
30 This has the properties that:\r
31 \r
32   start == 0 - no space at the front\r
33   end == alloc - no space at the end\r
34   end - start - number of elements in the queue\r
35 \r
36 We use a perl hash (HV *) to store the mapping from ids to priorities.\r
37 \r
38 */\r
39 struct poe_queue_tag {\r
40   /* the first entry in use */\r
41   int start;\r
42 \r
43   /* 1 past the last entry in use, hence end - start is the number of \r
44      entries in the queue */\r
45   int end;\r
46 \r
47   /* the total number of entries allocated */\r
48   int alloc;\r
49 \r
50   /* used to generate item ids */\r
51   pq_id_t queue_seq;\r
52 \r
53   /* used to track in use item ids */\r
54   HV *ids;\r
55 \r
56   /* the actual entries */\r
57   pq_entry *entries;\r
58 };\r
59 \r
60 /*\r
61 poe_create - create a new queue object.\r
62 \r
63 No parameters.  returns the new queue object.\r
64 \r
65 */\r
66 poe_queue *\r
67 pq_create(void) {\r
68   poe_queue *pq = mymalloc(sizeof(poe_queue));\r
69   \r
70   if (pq == NULL)\r
71     croak("Out of memory");\r
72   pq->start = 0;\r
73   pq->end = 0;\r
74   pq->alloc = PQ_START_SIZE;\r
75   pq->queue_seq = 0;\r
76   pq->ids = newHV();\r
77   pq->entries = mymalloc(sizeof(pq_entry) * PQ_START_SIZE);\r
78   memset(pq->entries, 0, sizeof(pq_entry) * PQ_START_SIZE);\r
79   if (pq->entries == NULL)\r
80     croak("Out of memory");\r
81 \r
82   DEBUG( fprintf(stderr, "pq_create() => %p\n", pq) );\r
83 \r
84   return pq;\r
85 }\r
86 \r
87 /*\r
88 pq_delete - release the queue object.\r
89 \r
90 This also releases one reference from each SV in the queue.\r
91 \r
92 */\r
93 void\r
94 pq_delete(poe_queue *pq) {\r
95   int i;\r
96 \r
97   DEBUG( fprintf(stderr, "pq_delete(%p)\n", pq) );\r
98   if (pq->end > pq->start) {\r
99     for (i = pq->start; i < pq->end; ++i) {\r
100       SvREFCNT_dec(pq->entries[i].payload);\r
101     }\r
102   }\r
103   SvREFCNT_dec((SV *)pq->ids);\r
104   pq->ids = NULL;\r
105   if (pq->entries)\r
106     myfree(pq->entries);\r
107   pq->entries = NULL;\r
108   myfree(pq);\r
109 }\r
110 \r
111 /*\r
112 pq_new_id - generate a new item id.\r
113 \r
114 Internal use only.\r
115 \r
116 This, the following 3 functions and pq_create, pq_delete, should be\r
117 all that needs to be modified if we change hash implementations.\r
118 \r
119 */\r
120 static\r
121 pq_id_t\r
122 pq_new_id(poe_queue *pq, pq_priority_t priority) {\r
123   int seq = ++pq->queue_seq;\r
124   SV *index = newSViv(seq);\r
125 \r
126   while (hv_exists_ent(pq->ids, index, 0)) {\r
127     seq = ++pq->queue_seq;\r
128     sv_setiv(index, seq);\r
129   }\r
130   hv_store_ent(pq->ids, index, newSVnv(priority), 0);\r
131 \r
132   return seq;\r
133 }\r
134 \r
135 /*\r
136 pq_release_id - releases an id for future use.\r
137 */\r
138 static\r
139 void\r
140 pq_release_id(poe_queue *pq, pq_id_t id) {\r
141   SV *id_sv = sv_2mortal(newSViv(id));\r
142 \r
143   hv_delete_ent(pq->ids, id_sv, 0, 0);\r
144 }\r
145 \r
146 /*\r
147 pq_item_priority - get the priority of an item given it's id\r
148 */\r
149 static\r
150 int\r
151 pq_item_priority(poe_queue *pq, pq_id_t id, pq_priority_t *priority) {\r
152   HE *entry = hv_fetch_ent(pq->ids, sv_2mortal(newSViv(id)), 0, 0);\r
153 \r
154   if (!entry)\r
155     return 0;\r
156 \r
157   *priority = SvNV(HeVAL(entry));\r
158 \r
159   return 1;\r
160 }\r
161 \r
162 /*\r
163 pq_set_id_priority - set the priority of an item in the id hash\r
164 */\r
165 static\r
166 void\r
167 pq_set_id_priority(poe_queue *pq, pq_id_t id, pq_priority_t new_priority) {\r
168   HE *entry = hv_fetch_ent(pq->ids, sv_2mortal(newSViv(id)), 0, 0);\r
169 \r
170   if (!entry)\r
171     croak("pq_set_priority: id not found");\r
172 \r
173   sv_setnv(HeVAL(entry), new_priority);\r
174 }\r
175 \r
176 /*\r
177 pq_move_items - moves items around.\r
178 \r
179 This encapsulates the old calls to memmove(), providing a single place\r
180 to add error checking.\r
181 */\r
182 static void\r
183 pq_move_items(poe_queue *pq, int target, int src, int count) {\r
184 \r
185   DEBUG_ERR(\r
186   {\r
187     int die = 0;\r
188     if (src < pq->start) {\r
189       fprintf(stderr, "src %d less than start %d\n", src, pq->start);\r
190       ++die;\r
191     }\r
192     if (src + count > pq->end) {\r
193       fprintf(stderr, "src %d + count %d beyond end %d\n", src, count, pq->end);\r
194       ++die;\r
195     }\r
196     if (target < 0) {\r
197       fprintf(stderr, "target %d < 0\n", target);\r
198       ++die;\r
199     }\r
200     if (target + count > pq->alloc) {\r
201       fprintf(stderr, "target %d + count %d > alloc\n", target, count, pq->alloc);\r
202       ++die;\r
203     }\r
204     if (die) *(char *)0 = '\0';\r
205   }\r
206   )\r
207   memmove(pq->entries + target, pq->entries + src, count * sizeof(pq_entry));\r
208 }\r
209 \r
210 /*\r
211 pq_realloc - make space at the front of back of the queue.\r
212 \r
213 This adjusts the queue to allow insertion of a single item at the\r
214 front or the back of the queue.\r
215 \r
216 If the queue has 33% or more space available we simple adjust the\r
217 position of the in-use items within the array.  We try not to push the\r
218 items right up against the opposite end of the array, since we might\r
219 need to insert items there too.\r
220 \r
221 If the queue has less than 33% space available we allocate another 50%\r
222 space.  We then only move the queue elements if we need space at the\r
223 front, since the reallocation has just opened up a huge space at the\r
224 back.  Since we're reallocating exponentially larger sizes we should\r
225 have a constant time cost on reallocation per queue item stored (but\r
226 other costs are going to be higher.)\r
227 \r
228 */\r
229 static\r
230 void\r
231 pq_realloc(poe_queue *pq, int at_end) {\r
232   int count = pq->end - pq->start;\r
233 \r
234   DEBUG( fprintf(stderr, "pq_realloc((%d, %d, %d), %d)\n", pq->start, pq->end, pq->alloc, at_end) );\r
235   if (count * 3 / 2 < pq->alloc) {\r
236     /* 33 % or more space available, use some of it */\r
237     int new_start;\r
238 \r
239     if (at_end) {\r
240       new_start = (pq->alloc - count) / 3;\r
241     }\r
242     else {\r
243       new_start = (pq->alloc - count) * 2 / 3;\r
244     }\r
245     DEBUG( fprintf(stderr, "  moving start to %d\n", new_start) );\r
246     pq_move_items(pq, new_start, pq->start, count);\r
247     pq->start = new_start;\r
248     pq->end = new_start + count;\r
249   }\r
250   else {\r
251     int new_alloc = pq->alloc * 3 / 2;\r
252     pq->entries = myrealloc(pq->entries, sizeof(pq_entry) * new_alloc);\r
253     pq->alloc = new_alloc;\r
254 \r
255     if (!pq->entries)\r
256       croak("Out of memory");\r
257 \r
258     DEBUG( fprintf(stderr, "  - expanding to %d entries\n", new_alloc) );\r
259 \r
260     if (!at_end) {\r
261       int new_start = (new_alloc - count) * 2 / 3;\r
262       DEBUG( fprintf(stderr, "  moving start to %d\n", new_start) );\r
263       pq_move_items(pq, new_start, pq->start, count);\r
264       pq->start = new_start;\r
265       pq->end = new_start + count;\r
266     }\r
267   }\r
268   DEBUG( fprintf(stderr, "  final: %d %d %d\n", pq->start, pq->end, pq->alloc) );\r
269 }\r
270 \r
271 /*\r
272 pq_insertion_point - figure out where to insert an item with the given\r
273 priority\r
274 \r
275 Internal.\r
276 */\r
277 static\r
278 int\r
279 pq_insertion_point(poe_queue *pq, pq_priority_t priority) {\r
280   /* for now this is just a linear search, later we should make it \r
281      binary */\r
282   int i = pq->end;\r
283   while (i > pq->start &&\r
284          priority < pq->entries[i-1].priority) {\r
285     --i;\r
286   }\r
287 \r
288   return i;\r
289 }\r
290 \r
291 int\r
292 pq_enqueue(poe_queue *pq, pq_priority_t priority, SV *payload) {\r
293   int fill_at;\r
294   pq_id_t id = pq_new_id(pq, priority);\r
295 \r
296   DEBUG( fprintf(stderr, "pq_enqueue(%f, %p)\n", priority, payload) );\r
297   if (pq->start == pq->end) {\r
298     DEBUG( fprintf(stderr, "  - on empty queue\n") );\r
299     /* allow room at front and back for new entries */\r
300     pq->start = pq->alloc / 3;\r
301     pq->end = pq->start + 1;\r
302     fill_at = pq->start;\r
303   }\r
304   else if (priority >= pq->entries[pq->end-1].priority) {\r
305     DEBUG( fprintf(stderr, "  - at the end\n") );\r
306     if (pq->end == pq->alloc)\r
307       /* past the end - need to realloc or make some space */\r
308       pq_realloc(pq, AT_END);\r
309     \r
310     fill_at = pq->end;\r
311     ++pq->end;\r
312   }\r
313   else if (priority < pq->entries[pq->start].priority) {\r
314     DEBUG( fprintf(stderr, "  - at the front\n") );\r
315     if (pq->start == 0)\r
316       /* no space at the front, make some */\r
317       pq_realloc(pq, AT_START);\r
318 \r
319     --pq->start;\r
320     fill_at = pq->start;\r
321   }\r
322   else {\r
323     int i;\r
324     DEBUG( fprintf(stderr, "  - in the middle\n") );\r
325     i = pq_insertion_point(pq, priority);\r
326     \r
327     /* if we're near the end we want to push entries up, otherwise down */\r
328     if (i - pq->start > (pq->end - pq->start) / 2) {\r
329       DEBUG( fprintf(stderr, "    - closer to the back (%d -> [ %d %d ])\n",\r
330                      i, pq->start, pq->end) );\r
331       /* make sure we have space, this might end up copying twice, \r
332          but too bad for now */\r
333       if (pq->end == pq->alloc) {\r
334         int old_start = pq->start;\r
335         pq_realloc(pq, AT_END);\r
336         i += pq->start - old_start;\r
337       }\r
338       \r
339       pq_move_items(pq, i+1, i, pq->end - i);\r
340       ++pq->end;\r
341       fill_at = i;\r
342     }\r
343     else {\r
344       DEBUG( fprintf(stderr, "    - closer to the front (%d -> [ %d %d ])\n",\r
345                      i, pq->start, pq->end) );\r
346       if (pq->start == 0) {\r
347         pq_realloc(pq, AT_START);\r
348         i += pq->start;\r
349       }\r
350       pq_move_items(pq, pq->start-1, pq->start, i - pq->start);\r
351       --pq->start;\r
352       fill_at = i-1;\r
353     }\r
354   }\r
355   pq->entries[fill_at].priority = priority;\r
356   pq->entries[fill_at].id = id;\r
357   pq->entries[fill_at].payload = newSVsv(payload);\r
358 \r
359   return id;\r
360 }\r
361 \r
362 /*\r
363   Note: it's up to the caller to release the SV.  The XS code does this \r
364   by making it mortal.\r
365 */\r
366 int\r
367 pq_dequeue_next(poe_queue *pq, pq_priority_t *priority, pq_id_t *id, SV **payload) {\r
368   pq_entry *entry;\r
369   /* the caller needs to release the payload (somehow) */\r
370   if (pq->start == pq->end)\r
371     return 0;\r
372 \r
373   entry = pq->entries + pq->start++;\r
374   *priority = entry->priority;\r
375   *id = entry->id;\r
376   *payload = entry->payload;\r
377   pq_release_id(pq, entry->id);\r
378 \r
379   return 1;\r
380 }\r
381 \r
382 int\r
383 pq_get_next_priority(poe_queue *pq, pq_priority_t *priority) {\r
384   if (pq->start == pq->end)\r
385     return 0;\r
386 \r
387   *priority = pq->entries[pq->start].priority;\r
388   return 1;\r
389 }\r
390 \r
391 int\r
392 pq_get_item_count(poe_queue *pq) {\r
393   return pq->end - pq->start;\r
394 }\r
395 \r
396 /*\r
397 pq_test_filter - the XS magic involved in passing the payload to a\r
398 filter function.\r
399 */\r
400 static\r
401 int\r
402 pq_test_filter(pq_entry *entry, SV *filter) {\r
403   /* man perlcall for the magic here */\r
404   dSP;\r
405   int count;\r
406   SV *result_sv;\r
407   int result;\r
408 \r
409   ENTER;\r
410   SAVETMPS;\r
411   PUSHMARK(SP);\r
412   XPUSHs(sv_2mortal(newSVsv(entry->payload)));\r
413   PUTBACK;\r
414 \r
415   count = call_sv(filter, G_SCALAR);\r
416 \r
417   SPAGAIN;\r
418 \r
419   if (count != 1) \r
420     croak("got other than 1 value in scalar context");\r
421 \r
422   result_sv = POPs;\r
423   result = SvTRUE(result_sv);\r
424 \r
425   PUTBACK;\r
426   FREETMPS;\r
427   LEAVE;\r
428 \r
429   return result;\r
430 }\r
431 \r
432 /*\r
433 pq_find_item - search for an item we know is there.\r
434 \r
435 Internal.\r
436 */\r
437 static\r
438 int\r
439 pq_find_item(poe_queue *pq, pq_id_t id, pq_priority_t priority) {\r
440   int i;\r
441 \r
442   for (i = pq->start; i < pq->end; ++i) {\r
443     if (pq->entries[i].id == id)\r
444       return i;\r
445   }\r
446   DEBUG(fprintf(stderr, "pq_find_item %d => %f\n", id, priority) );\r
447   croak("Internal inconsistency: event should have been found");\r
448 }\r
449 \r
450 int\r
451 pq_remove_item(poe_queue *pq, pq_id_t id, SV *filter, pq_entry *removed) {\r
452   pq_priority_t priority;\r
453   int index;\r
454 \r
455   if (!pq_item_priority(pq, id, &priority)) {\r
456     errno = ESRCH;\r
457     return 0;\r
458   }\r
459 \r
460   index = pq_find_item(pq, id, priority);\r
461 \r
462   if (!pq_test_filter(pq->entries + index, filter)) {\r
463     errno = EPERM;\r
464     return 0;\r
465   }\r
466 \r
467   *removed = pq->entries[index];\r
468   pq_release_id(pq, id);\r
469   if (index == pq->start) {\r
470     ++pq->start;\r
471   }\r
472   else if (index == pq->end - 1) {\r
473     --pq->end;\r
474   }\r
475   else {\r
476     pq_move_items(pq, index, index+1, pq->end - index - 1);\r
477     --pq->end;\r
478   }\r
479   DEBUG( fprintf(stderr, "removed (%d, %p (%d))\n", id, removed->payload,\r
480                  SvREFCNT(removed->payload)) );\r
481 \r
482   return 1;\r
483 }\r
484 \r
485 int\r
486 pq_remove_items(poe_queue *pq, SV *filter, int max_count, pq_entry **entries) {\r
487   int in_index, out_index;\r
488   int remove_count = 0;\r
489   \r
490   *entries = NULL;\r
491   if (pq->start == pq->end)\r
492     return 0;\r
493 \r
494   *entries = mymalloc(sizeof(pq_entry) * (pq->end - pq->start));\r
495   if (!*entries)\r
496     croak("Out of memory");\r
497   \r
498   in_index = out_index = pq->start;\r
499   while (in_index < pq->end && remove_count < max_count) {\r
500     if (pq_test_filter(pq->entries + in_index, filter)) {\r
501       pq_release_id(pq, pq->entries[in_index].id);\r
502       (*entries)[remove_count++] = pq->entries[in_index++];\r
503     }\r
504     else {\r
505       pq->entries[out_index++] = pq->entries[in_index++];\r
506     }\r
507   }\r
508   while (in_index < pq->end) {\r
509     pq->entries[out_index++] = pq->entries[in_index++];\r
510   }\r
511   pq->end = out_index;\r
512   \r
513   return remove_count;\r
514 }\r
515 \r
516 /*\r
517 We need to keep the following 2 functions in sync (or combine the\r
518 common code.)\r
519 */\r
520 int\r
521 pq_set_priority(poe_queue *pq, pq_id_t id, SV *filter, pq_priority_t new_priority) {\r
522   pq_priority_t old_priority;\r
523   int index, insert_at;\r
524 \r
525   if (!pq_item_priority(pq, id, &old_priority)) {\r
526     errno = ESRCH;\r
527     return 0;\r
528   }\r
529 \r
530   index = pq_find_item(pq, id, old_priority);\r
531 \r
532   if (!pq_test_filter(pq->entries + index, filter)) {\r
533     errno = EPERM;\r
534     return 0;\r
535   }\r
536 \r
537   DEBUG( fprintf(stderr, " - index %d  oldp %f newp %f\n", index, old_priority, new_priority) );\r
538 \r
539   if (pq->end - pq->start == 1) {\r
540     DEBUG( fprintf(stderr, "   -- one item\n") );\r
541     /* only the one item anyway */\r
542     pq->entries[pq->start].priority = new_priority;\r
543   }\r
544   else {\r
545     insert_at = pq_insertion_point(pq, new_priority);\r
546     DEBUG( fprintf(stderr, "   - new index %d\n", insert_at) );\r
547     /* the item is still in the queue, so either side of it means it\r
548        won't move */\r
549     if (insert_at == index || insert_at == index+1) {\r
550       DEBUG( fprintf(stderr, "   -- change in place\n") );\r
551       pq->entries[index].priority = new_priority;\r
552     }\r
553     else {\r
554       pq_entry saved = pq->entries[index];\r
555       saved.priority = new_priority;\r
556 \r
557       if (insert_at < index) {\r
558         DEBUG( fprintf(stderr, "  - insert_at < index\n") );\r
559         pq_move_items(pq, insert_at + 1, insert_at, index - insert_at);\r
560         pq->entries[insert_at] = saved;\r
561       }\r
562       else {\r
563         DEBUG( fprintf(stderr, "  - insert_at > index\n") );\r
564         --insert_at;\r
565         pq_move_items(pq, index, index + 1, insert_at - index);\r
566         pq->entries[insert_at] = saved;\r
567       }\r
568     }\r
569   }\r
570 \r
571   pq_set_id_priority(pq, id, new_priority);\r
572 \r
573   return 1;  \r
574 }\r
575 \r
576 int\r
577 pq_adjust_priority(poe_queue *pq, pq_id_t id, SV *filter, double delta, pq_priority_t *priority) {\r
578   pq_priority_t old_priority, new_priority;\r
579   int index, insert_at;\r
580 \r
581   DEBUG( fprintf(stderr, "pq_adjust_priority(..., %d, %p, %f, ...)\n", id, filter, delta) );\r
582 \r
583   if (!pq_item_priority(pq, id, &old_priority)) {\r
584     errno = ESRCH;\r
585     return 0;\r
586   }\r
587 \r
588   index = pq_find_item(pq, id, old_priority);\r
589 \r
590   if (!pq_test_filter(pq->entries + index, filter)) {\r
591     errno = EPERM;\r
592     return 0;\r
593   }\r
594 \r
595   new_priority = old_priority + delta;\r
596 \r
597   DEBUG( fprintf(stderr, " - index %d  oldp %f newp %f\n", index, old_priority, new_priority) );\r
598 \r
599   if (pq->end - pq->start == 1) {\r
600     DEBUG( fprintf(stderr, "   -- one item\n") );\r
601     /* only the one item anyway */\r
602     pq->entries[pq->start].priority = new_priority;\r
603   }\r
604   else {\r
605     insert_at = pq_insertion_point(pq, new_priority);\r
606     DEBUG( fprintf(stderr, "   - new index %d\n", insert_at) );\r
607     /* the item is still in the queue, so either side of it means it\r
608        won't move */\r
609     if (insert_at == index || insert_at == index+1) {\r
610       DEBUG( fprintf(stderr, "   -- change in place\n") );\r
611       pq->entries[index].priority = new_priority;\r
612     }\r
613     else {\r
614       pq_entry saved = pq->entries[index];\r
615       saved.priority = new_priority;\r
616 \r
617       if (insert_at < index) {\r
618         DEBUG( fprintf(stderr, "  - insert_at < index\n") );\r
619         pq_move_items(pq, insert_at + 1, insert_at, index - insert_at);\r
620         pq->entries[insert_at] = saved;\r
621       }\r
622       else {\r
623         DEBUG( fprintf(stderr, "  - insert_at > index\n") );\r
624         --insert_at;\r
625         pq_move_items(pq, index, index + 1, insert_at - index);\r
626         pq->entries[insert_at] = saved;\r
627       }\r
628     }\r
629   }\r
630 \r
631   pq_set_id_priority(pq, id, new_priority);\r
632   *priority = new_priority;\r
633 \r
634   return 1;  \r
635 }\r
636 \r
637 int\r
638 pq_peek_items(poe_queue *pq, SV *filter, int max_count, pq_entry **items) {\r
639   int count = 0;\r
640   int i;\r
641 \r
642   *items = NULL;\r
643   if (pq->end == pq->start)\r
644     return 0;\r
645 \r
646   *items = mymalloc(sizeof(pq_entry) * (pq->end - pq->start));\r
647   for (i = pq->start; i < pq->end; ++i) {\r
648     if (pq_test_filter(pq->entries + i, filter)) {\r
649       (*items)[count++] = pq->entries[i];\r
650     }\r
651   }\r
652   if (!count) {\r
653     myfree(*items);\r
654     *items = NULL;\r
655   }\r
656 \r
657   return count;\r
658 }\r
659 \r
660 /*\r
661 pq_dump - dump the internals of the queue structure.\r
662 */\r
663 void\r
664 pq_dump(poe_queue *pq) {\r
665   int i;\r
666   HE *he;\r
667 \r
668   fprintf(stderr, "poe_queue\n");\r
669   fprintf(stderr, "  start: %d\n", pq->start);\r
670   fprintf(stderr, "    end: %d\n", pq->end);\r
671   fprintf(stderr, "  alloc: %d\n", pq->alloc);\r
672   fprintf(stderr, "    seq: %d\n", pq->queue_seq);\r
673   fprintf(stderr, "  **Queue Entries:\n"\r
674          "      index:   id  priority    SV\n");\r
675   for (i = pq->start; i < pq->end; ++i) {\r
676     pq_entry *entry = pq->entries + i;\r
677     fprintf(stderr, "      %5d: %5d %8f  %p (%u)\n", i, entry->id, entry->priority,\r
678            entry->payload, (unsigned)SvREFCNT(entry->payload));\r
679   }\r
680   fprintf(stderr, "  **Hash entries:\n");\r
681   hv_iterinit(pq->ids);\r
682   while ((he = hv_iternext(pq->ids)) != NULL) {\r
683     STRLEN len;\r
684     fprintf(stderr, "   %s => %f\n", HePV(he, len), SvNV(hv_iterval(pq->ids, he)));\r
685   }\r
686 }\r