Skip to content

Commit 1817aa8

Browse files
committed
store: track HashPosition for first and last elements
this produces a huge performance improvement for queues with large internal tables. an internal table of large size may appear if the array had lots of elements inserted into it and later deleted. this resulted in major performance losses for the reader of the elements, as zend_hash_internal_pointer_reset_ex() had to scan through many IS_UNDEF offsets to find the actual first element. there are two ways to attack this problem: 1) reallocate the internal table as elements are deleted to reduce the internal table size - this proved to be relatively ineffective 2) track the start and end of the hashtable to avoid repeated scans during every shift() call - this is the approach taken in this commit, and provides major performance benefits the test case written in #42 now runs to completion substantially faster, without any performance degradation. more tests are needed to ensure that this works fully as intended, but I chose to take the safe route with invalidating vs updating the offsets, so I think it should be good.
1 parent eed719e commit 1817aa8

File tree

2 files changed

+56
-17
lines changed

2 files changed

+56
-17
lines changed

src/store.c

+54-17
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ void pmmpthread_store_init(pmmpthread_store_t* store) {
4141
zend_hash_init(
4242
&store->hash, 8, NULL,
4343
(dtor_func_t)pmmpthread_store_storage_dtor, 1);
44+
store->first = HT_INVALID_IDX;
45+
store->last = HT_INVALID_IDX;
4446
} /* }}} */
4547

4648
/* {{{ */
@@ -286,6 +288,10 @@ int pmmpthread_store_delete(zend_object *object, zval *key) {
286288
if (result == SUCCESS && was_pmmpthread_object) {
287289
_pmmpthread_store_bump_modcount_nolock(threaded);
288290
}
291+
//TODO: it would be better if we can update this, if we deleted the first element
292+
ts_obj->props.first = HT_INVALID_IDX;
293+
ts_obj->props.last = HT_INVALID_IDX;
294+
289295
//TODO: sync local properties?
290296
pmmpthread_monitor_unlock(&ts_obj->monitor);
291297
} else result = FAILURE;
@@ -414,6 +420,8 @@ static inline zend_bool pmmpthread_store_update_shared_property(pmmpthread_objec
414420
}
415421
}
416422
}
423+
ts_obj->props.first = HT_INVALID_IDX;
424+
ts_obj->props.last = HT_INVALID_IDX;
417425

418426
return result;
419427
}
@@ -538,6 +546,12 @@ int pmmpthread_store_write(zend_object *object, zval *key, zval *write, zend_boo
538546
if (result == SUCCESS && was_pmmpthread_object) {
539547
_pmmpthread_store_bump_modcount_nolock(threaded);
540548
}
549+
if (key) {
550+
//only invalidate position if an arbitrary key was used
551+
//if the item was appended, the first element was either unchanged or the position was invalid anyway
552+
ts_obj->props.first = HT_INVALID_IDX;
553+
ts_obj->props.last = HT_INVALID_IDX;
554+
}
541555
//this isn't necessary for any specific property write, but since we don't have any other way to clean up local
542556
//cached ThreadSafe references that are dead, we have to take the opportunity
543557
pmmpthread_store_sync_local_properties(object);
@@ -576,12 +590,20 @@ int pmmpthread_store_shift(zend_object *object, zval *member) {
576590

577591
if (pmmpthread_monitor_lock(&ts_obj->monitor)) {
578592
zval key;
579-
HashPosition position;
580593
zval *zstorage;
581594

582-
zend_hash_internal_pointer_reset_ex(&ts_obj->props.hash, &position);
583-
if ((zstorage = zend_hash_get_current_data_ex(&ts_obj->props.hash, &position))) {
584-
zend_hash_get_current_key_zval_ex(&ts_obj->props.hash, &key, &position);
595+
if (ts_obj->props.first == HT_INVALID_IDX) {
596+
zend_hash_internal_pointer_reset_ex(&ts_obj->props.hash, &ts_obj->props.first);
597+
}
598+
if ((zstorage = zend_hash_get_current_data_ex(&ts_obj->props.hash, &ts_obj->props.first))) {
599+
zend_hash_get_current_key_zval_ex(&ts_obj->props.hash, &key, &ts_obj->props.first);
600+
601+
//the new first element will now be the next item, if there is one
602+
if (zend_hash_move_forward_ex(&ts_obj->props.hash, &ts_obj->props.first) == FAILURE) {
603+
ts_obj->props.first = HT_INVALID_IDX;
604+
ts_obj->props.last = HT_INVALID_IDX;
605+
}
606+
585607
zend_bool may_be_locally_cached;
586608

587609
pmmpthread_store_restore_zval_ex(member, zstorage, &may_be_locally_cached);
@@ -617,17 +639,22 @@ int pmmpthread_store_chunk(zend_object *object, zend_long size, zend_bool preser
617639
pmmpthread_object_t *ts_obj = threaded->ts_obj;
618640

619641
if (pmmpthread_monitor_lock(&ts_obj->monitor)) {
620-
HashPosition position;
621642
zval *zstorage;
622643

623644
array_init(chunk);
624-
zend_hash_internal_pointer_reset_ex(&ts_obj->props.hash, &position);
645+
if (ts_obj->props.first == HT_INVALID_IDX) {
646+
zend_hash_internal_pointer_reset_ex(&ts_obj->props.hash, &ts_obj->props.first);
647+
}
625648
zend_bool stale_local_cache = 0;
626649
while((zend_hash_num_elements(Z_ARRVAL_P(chunk)) < size) &&
627-
(zstorage = zend_hash_get_current_data_ex(&ts_obj->props.hash, &position))) {
650+
(zstorage = zend_hash_get_current_data_ex(&ts_obj->props.hash, &ts_obj->props.first))) {
628651
zval key, zv;
629652

630-
zend_hash_get_current_key_zval_ex(&ts_obj->props.hash, &key, &position);
653+
zend_hash_get_current_key_zval_ex(&ts_obj->props.hash, &key, &ts_obj->props.first);
654+
if (zend_hash_move_forward_ex(&ts_obj->props.hash, &ts_obj->props.first) == FAILURE) {
655+
ts_obj->props.first = HT_INVALID_IDX;
656+
ts_obj->props.last = HT_INVALID_IDX;
657+
}
631658

632659
zend_bool may_be_locally_cached;
633660
pmmpthread_store_restore_zval_ex(&zv, zstorage, &may_be_locally_cached);
@@ -651,8 +678,6 @@ int pmmpthread_store_chunk(zend_object *object, zend_long size, zend_bool preser
651678
}
652679
zend_string_release(Z_STR(key));
653680
}
654-
655-
zend_hash_internal_pointer_reset_ex(&ts_obj->props.hash, &position);
656681
}
657682
if (stale_local_cache) {
658683
_pmmpthread_store_bump_modcount_nolock(threaded);
@@ -673,12 +698,18 @@ int pmmpthread_store_pop(zend_object *object, zval *member) {
673698

674699
if (pmmpthread_monitor_lock(&ts_obj->monitor)) {
675700
zval key;
676-
HashPosition position;
677701
zval *zstorage;
678702

679-
zend_hash_internal_pointer_end_ex(&ts_obj->props.hash, &position);
680-
if ((zstorage = zend_hash_get_current_data_ex(&ts_obj->props.hash, &position))) {
681-
zend_hash_get_current_key_zval_ex(&ts_obj->props.hash, &key, &position);
703+
if (ts_obj->props.last == HT_INVALID_IDX) {
704+
zend_hash_internal_pointer_end_ex(&ts_obj->props.hash, &ts_obj->props.last);
705+
}
706+
if ((zstorage = zend_hash_get_current_data_ex(&ts_obj->props.hash, &ts_obj->props.last))) {
707+
zend_hash_get_current_key_zval_ex(&ts_obj->props.hash, &key, &ts_obj->props.last);
708+
709+
if (zend_hash_move_backwards_ex(&ts_obj->props.hash, &ts_obj->props.last) == FAILURE) {
710+
ts_obj->props.first = HT_INVALID_IDX;
711+
ts_obj->props.last = HT_INVALID_IDX;
712+
}
682713

683714
zend_bool may_be_locally_cached;
684715
pmmpthread_store_restore_zval_ex(member, zstorage, &may_be_locally_cached);
@@ -1313,9 +1344,15 @@ void pmmpthread_store_reset(zend_object *object, HashPosition *position) {
13131344
pmmpthread_object_t *ts_obj = PMMPTHREAD_FETCH_TS_FROM(object);
13141345

13151346
if (pmmpthread_monitor_lock(&ts_obj->monitor)) {
1316-
zend_hash_internal_pointer_reset_ex(&ts_obj->props.hash, position);
1317-
if (zend_hash_has_more_elements_ex(&ts_obj->props.hash, position) == FAILURE) { //empty
1318-
*position = HT_INVALID_IDX;
1347+
if (ts_obj->props.first == HT_INVALID_IDX) {
1348+
zend_hash_internal_pointer_reset_ex(&ts_obj->props.hash, position);
1349+
if (zend_hash_has_more_elements_ex(&ts_obj->props.hash, position) == FAILURE) { //empty
1350+
*position = HT_INVALID_IDX;
1351+
} else {
1352+
ts_obj->props.first = *position;
1353+
}
1354+
} else {
1355+
*position = ts_obj->props.first;
13191356
}
13201357
pmmpthread_monitor_unlock(&ts_obj->monitor);
13211358
}

src/store.h

+2
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@
3232
typedef struct _pmmpthread_store_t {
3333
HashTable hash;
3434
zend_long modcount;
35+
HashPosition first;
36+
HashPosition last;
3537
} pmmpthread_store_t;
3638

3739
void pmmpthread_store_init(pmmpthread_store_t* store);

0 commit comments

Comments
 (0)