Skip to content

Commit 3a05cda

Browse files
authored
SplPriorityQueue performance improvements (#6859)
This optimization is targeting cases when a SplPriorityQueue instance is used exclusively with double or long priorities. During the first insertion into an empty queue, the comparator is changed to the specialized one if the priority of inserted inserted key is long or double. During insertion to non-empty queue, comparator is swapped back to the generic one on type conflict. As a result code like following, where the weight field is always double or int, runs almost twice as fast. foreach ($items as $item) { $pqueue->insert($item, -$item->weight); if ($pqueue->count() > $size) { $pqueue->extract(); } }
1 parent 12c5589 commit 3a05cda

File tree

1 file changed

+40
-1
lines changed

1 file changed

+40
-1
lines changed

ext/spl/spl_heap.c

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,15 @@ static zend_always_inline void *spl_heap_elem(spl_ptr_heap *heap, size_t i) {
9797

9898
static zend_always_inline void spl_heap_elem_copy(spl_ptr_heap *heap, void *to, void *from) {
9999
assert(to != from);
100-
memcpy(to, from, heap->elem_size);
100+
101+
/* Specialized for cases of heap and priority queue. With the size being
102+
* constant known at compile time the compiler can fully inline calls to memcpy. */
103+
if (heap->elem_size == sizeof(spl_pqueue_elem)) {
104+
memcpy(to, from, sizeof(spl_pqueue_elem));
105+
} else {
106+
ZEND_ASSERT(heap->elem_size == sizeof(zval));
107+
memcpy(to, from, sizeof(zval));
108+
}
101109
}
102110

103111
static void spl_ptr_heap_zval_dtor(void *elem) { /* {{{ */
@@ -237,6 +245,22 @@ static int spl_ptr_pqueue_elem_cmp(void *x, void *y, zval *object) { /* {{{ */
237245
}
238246
/* }}} */
239247

248+
/* Specialized comparator used when we are absolutely sure an instance of the
249+
* not inherited SplPriorityQueue class contains only priorities as longs. This
250+
* fact is tracked during insertion into the queue. */
251+
static int spl_ptr_pqueue_elem_cmp_long(void *x, void *y, zval *object) {
252+
zend_long a = Z_LVAL(((spl_pqueue_elem*) x)->priority);
253+
zend_long b = Z_LVAL(((spl_pqueue_elem*) y)->priority);
254+
return a>b ? 1 : (a<b ? -1 : 0);
255+
}
256+
257+
/* same as spl_ptr_pqueue_elem_cmp_long */
258+
static int spl_ptr_pqueue_elem_cmp_double(void *x, void *y, zval *object) {
259+
double a = Z_DVAL(((spl_pqueue_elem*) x)->priority);
260+
double b = Z_DVAL(((spl_pqueue_elem*) y)->priority);
261+
return ZEND_NORMALIZE_BOOL(a - b);
262+
}
263+
240264
static spl_ptr_heap *spl_ptr_heap_init(spl_ptr_heap_cmp_func cmp, spl_ptr_heap_ctor_func ctor, spl_ptr_heap_dtor_func dtor, size_t elem_size) /* {{{ */
241265
{
242266
spl_ptr_heap *heap = emalloc(sizeof(spl_ptr_heap));
@@ -653,6 +677,21 @@ PHP_METHOD(SplPriorityQueue, insert)
653677
ZVAL_COPY(&elem.data, data);
654678
ZVAL_COPY(&elem.priority, priority);
655679

680+
/* If we know this call came from non inherited SplPriorityQueue it's
681+
* possible to do specialization on the type of the priority parameter. */
682+
if (!intern->fptr_cmp) {
683+
int type = Z_TYPE(elem.priority);
684+
spl_ptr_heap_cmp_func new_cmp =
685+
(type == IS_LONG) ? spl_ptr_pqueue_elem_cmp_long :
686+
((type == IS_DOUBLE) ? spl_ptr_pqueue_elem_cmp_double : spl_ptr_pqueue_elem_cmp);
687+
688+
if (intern->heap->count == 0) { /* Specialize empty queue */
689+
intern->heap->cmp = new_cmp;
690+
} else if (new_cmp != intern->heap->cmp) { /* Despecialize on type conflict. */
691+
intern->heap->cmp = spl_ptr_pqueue_elem_cmp;
692+
}
693+
}
694+
656695
spl_ptr_heap_insert(intern->heap, &elem, ZEND_THIS);
657696

658697
RETURN_TRUE;

0 commit comments

Comments
 (0)