Skip to content

Commit b2b6100

Browse files
authored
store: track HashPosition for first and last elements (#134)
This produces a significant 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.
1 parent c58ba9b commit b2b6100

File tree

2 files changed

+76
-11
lines changed

2 files changed

+76
-11
lines changed

Diff for: src/store.c

+74-11
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,19 @@ static void pmmpthread_store_restore_zval_ex(zval* unstore, zval* zstorage, zend
3535
static void pmmpthread_store_restore_zval(zval* unstore, zval* zstorage); /* }}} */
3636
static void pmmpthread_store_storage_dtor(zval* element);
3737

38+
/* {{{ */
39+
static inline void pmmpthread_store_invalidate_bounds(pmmpthread_store_t* store) {
40+
store->first = HT_INVALID_IDX;
41+
store->last = HT_INVALID_IDX;
42+
} /* }}} */
43+
3844
/* {{{ */
3945
void pmmpthread_store_init(pmmpthread_store_t* store) {
4046
store->modcount = 0;
4147
zend_hash_init(
4248
&store->hash, 8, NULL,
4349
(dtor_func_t)pmmpthread_store_storage_dtor, 1);
50+
pmmpthread_store_invalidate_bounds(store);
4451
} /* }}} */
4552

4653
/* {{{ */
@@ -286,6 +293,9 @@ int pmmpthread_store_delete(zend_object *object, zval *key) {
286293
if (result == SUCCESS && was_pmmpthread_object) {
287294
_pmmpthread_store_bump_modcount_nolock(threaded);
288295
}
296+
//TODO: it would be better if we can update this, if we deleted the first element
297+
pmmpthread_store_invalidate_bounds(&ts_obj->props);
298+
289299
//TODO: sync local properties?
290300
pmmpthread_monitor_unlock(&ts_obj->monitor);
291301
} else result = FAILURE;
@@ -538,6 +548,24 @@ int pmmpthread_store_write(zend_object *object, zval *key, zval *write, zend_boo
538548
if (result == SUCCESS && was_pmmpthread_object) {
539549
_pmmpthread_store_bump_modcount_nolock(threaded);
540550
}
551+
if (ts_obj->props.first != HT_INVALID_IDX && ts_obj->props.first != 0) {
552+
HashPosition start = 0;
553+
if (zend_hash_get_current_data_ex(&ts_obj->props.hash, &start) != NULL) {
554+
//a table rehash may have occurred, moving all elements to the start of the table
555+
//this is usually because the table size was increased to accommodate the new element
556+
pmmpthread_store_invalidate_bounds(&ts_obj->props);
557+
}
558+
}
559+
if (key) {
560+
//only invalidate position if an arbitrary key was used
561+
//if the item was appended, the first element was either unchanged or the position was invalid anyway
562+
pmmpthread_store_invalidate_bounds(&ts_obj->props);
563+
} else if (ts_obj->props.last != HT_INVALID_IDX) {
564+
//if we appended, the last element is now the new item
565+
if (zend_hash_move_forward_ex(&ts_obj->props.hash, &ts_obj->props.last) == FAILURE) {
566+
ZEND_ASSERT(0);
567+
}
568+
}
541569
//this isn't necessary for any specific property write, but since we don't have any other way to clean up local
542570
//cached ThreadSafe references that are dead, we have to take the opportunity
543571
pmmpthread_store_sync_local_properties(object);
@@ -576,12 +604,22 @@ int pmmpthread_store_shift(zend_object *object, zval *member) {
576604

577605
if (pmmpthread_monitor_lock(&ts_obj->monitor)) {
578606
zval key;
579-
HashPosition position;
580607
zval *zstorage;
608+
HashPosition position = ts_obj->props.first;
581609

582-
zend_hash_internal_pointer_reset_ex(&ts_obj->props.hash, &position);
610+
if (position == HT_INVALID_IDX) {
611+
zend_hash_internal_pointer_reset_ex(&ts_obj->props.hash, &position);
612+
}
583613
if ((zstorage = zend_hash_get_current_data_ex(&ts_obj->props.hash, &position))) {
584614
zend_hash_get_current_key_zval_ex(&ts_obj->props.hash, &key, &position);
615+
616+
if (zend_hash_num_elements(&ts_obj->props.hash) == 1) { //we're about to delete the last element
617+
pmmpthread_store_invalidate_bounds(&ts_obj->props);
618+
} else {
619+
zend_hash_move_forward_ex(&ts_obj->props.hash, &position);
620+
ts_obj->props.first = position;
621+
}
622+
585623
zend_bool may_be_locally_cached;
586624

587625
pmmpthread_store_restore_zval_ex(member, zstorage, &may_be_locally_cached);
@@ -617,18 +655,28 @@ int pmmpthread_store_chunk(zend_object *object, zend_long size, zend_bool preser
617655
pmmpthread_object_t *ts_obj = threaded->ts_obj;
618656

619657
if (pmmpthread_monitor_lock(&ts_obj->monitor)) {
620-
HashPosition position;
621658
zval *zstorage;
622659

660+
HashPosition position = ts_obj->props.first;
661+
if (position == HT_INVALID_IDX) {
662+
zend_hash_internal_pointer_reset_ex(&ts_obj->props.hash, &position);
663+
}
623664
array_init(chunk);
624-
zend_hash_internal_pointer_reset_ex(&ts_obj->props.hash, &position);
625665
zend_bool stale_local_cache = 0;
626666
while((zend_hash_num_elements(Z_ARRVAL_P(chunk)) < size) &&
627667
(zstorage = zend_hash_get_current_data_ex(&ts_obj->props.hash, &position))) {
668+
628669
zval key, zv;
629670

630671
zend_hash_get_current_key_zval_ex(&ts_obj->props.hash, &key, &position);
631672

673+
if (zend_hash_num_elements(&ts_obj->props.hash) == 1) { //we're about to delete the last element
674+
pmmpthread_store_invalidate_bounds(&ts_obj->props);
675+
} else {
676+
zend_hash_move_forward_ex(&ts_obj->props.hash, &position);
677+
ts_obj->props.first = position;
678+
}
679+
632680
zend_bool may_be_locally_cached;
633681
pmmpthread_store_restore_zval_ex(&zv, zstorage, &may_be_locally_cached);
634682
if (!stale_local_cache) {
@@ -651,8 +699,6 @@ int pmmpthread_store_chunk(zend_object *object, zend_long size, zend_bool preser
651699
}
652700
zend_string_release(Z_STR(key));
653701
}
654-
655-
zend_hash_internal_pointer_reset_ex(&ts_obj->props.hash, &position);
656702
}
657703
if (stale_local_cache) {
658704
_pmmpthread_store_bump_modcount_nolock(threaded);
@@ -673,13 +719,22 @@ int pmmpthread_store_pop(zend_object *object, zval *member) {
673719

674720
if (pmmpthread_monitor_lock(&ts_obj->monitor)) {
675721
zval key;
676-
HashPosition position;
677722
zval *zstorage;
723+
HashPosition position = ts_obj->props.last;
678724

679-
zend_hash_internal_pointer_end_ex(&ts_obj->props.hash, &position);
725+
if (position == HT_INVALID_IDX) {
726+
zend_hash_internal_pointer_end_ex(&ts_obj->props.hash, &position);
727+
}
680728
if ((zstorage = zend_hash_get_current_data_ex(&ts_obj->props.hash, &position))) {
681729
zend_hash_get_current_key_zval_ex(&ts_obj->props.hash, &key, &position);
682730

731+
if (zend_hash_num_elements(&ts_obj->props.hash) == 1) { //we're about to delete the last element
732+
pmmpthread_store_invalidate_bounds(&ts_obj->props);
733+
} else {
734+
zend_hash_move_backwards_ex(&ts_obj->props.hash, &position);
735+
ts_obj->props.last = position;
736+
}
737+
683738
zend_bool may_be_locally_cached;
684739
pmmpthread_store_restore_zval_ex(member, zstorage, &may_be_locally_cached);
685740

@@ -1195,6 +1250,8 @@ int pmmpthread_store_merge(zend_object *destination, zval *from, zend_bool overw
11951250
if (overwrote_pmmpthread_object) {
11961251
_pmmpthread_store_bump_modcount_nolock(PMMPTHREAD_FETCH_FROM(destination));
11971252
}
1253+
pmmpthread_store_invalidate_bounds(&threaded[0]->props);
1254+
11981255
//TODO: sync local properties?
11991256

12001257
pmmpthread_monitor_unlock(&threaded[1]->monitor);
@@ -1313,9 +1370,15 @@ void pmmpthread_store_reset(zend_object *object, HashPosition *position) {
13131370
pmmpthread_object_t *ts_obj = PMMPTHREAD_FETCH_TS_FROM(object);
13141371

13151372
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;
1373+
if (ts_obj->props.first == HT_INVALID_IDX) {
1374+
zend_hash_internal_pointer_reset_ex(&ts_obj->props.hash, position);
1375+
if (zend_hash_has_more_elements_ex(&ts_obj->props.hash, position) == FAILURE) { //empty
1376+
*position = HT_INVALID_IDX;
1377+
} else {
1378+
ts_obj->props.first = *position;
1379+
}
1380+
} else {
1381+
*position = ts_obj->props.first;
13191382
}
13201383
pmmpthread_monitor_unlock(&ts_obj->monitor);
13211384
}

Diff for: 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)