Skip to content

Commit c17ee70

Browse files
committed
Optimize SplObjectStorage native read/write dimension handlers
This makes reading/writing with `$splObjectStorage[$offset]` shorthand twice as fast as it was previously, and a bit faster than offsetGet/offsetSet instead of (previously) much slower. Related to phpGH-7690
1 parent ee3caef commit c17ee70

File tree

2 files changed

+183
-11
lines changed

2 files changed

+183
-11
lines changed

ext/spl/spl_observer.c

Lines changed: 115 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -44,13 +44,19 @@ PHPAPI zend_class_entry *spl_ce_MultipleIterator;
4444

4545
PHPAPI zend_object_handlers spl_handler_SplObjectStorage;
4646

47+
typedef struct _spl_SplObjectStorage_fptrs {
48+
zend_function *get_hash;
49+
bool override_read_dimension;
50+
bool override_write_dimension;
51+
} spl_SplObjectStorage_fptrs;
52+
4753
typedef struct _spl_SplObjectStorage { /* {{{ */
48-
HashTable storage;
49-
zend_long index;
50-
HashPosition pos;
51-
zend_long flags;
52-
zend_function *fptr_get_hash;
53-
zend_object std;
54+
HashTable storage;
55+
zend_long index;
56+
HashPosition pos;
57+
zend_long flags;
58+
spl_SplObjectStorage_fptrs *fptrs;
59+
zend_object std;
5460
} spl_SplObjectStorage; /* }}} */
5561

5662
/* {{{ storage is an assoc array of [zend_object*]=>[zval *obj, zval *inf] */
@@ -73,15 +79,19 @@ void spl_SplObjectStorage_free_storage(zend_object *object) /* {{{ */
7379
zend_object_std_dtor(&intern->std);
7480

7581
zend_hash_destroy(&intern->storage);
82+
83+
if (UNEXPECTED(intern->fptrs)) {
84+
efree_size(intern->fptrs, sizeof(spl_SplObjectStorage_fptrs));
85+
}
7686
} /* }}} */
7787

7888
static int spl_object_storage_get_hash(zend_hash_key *key, spl_SplObjectStorage *intern, zend_object *obj) {
79-
if (intern->fptr_get_hash) {
89+
if (UNEXPECTED(intern->fptrs) && intern->fptrs->get_hash) {
8090
zval param;
8191
zval rv;
8292
ZVAL_OBJ(&param, obj);
8393
zend_call_method_with_1_params(
84-
&intern->std, intern->std.ce, &intern->fptr_get_hash, "getHash", &rv, &param);
94+
&intern->std, intern->std.ce, &intern->fptrs->get_hash, "getHash", &rv, &param);
8595
if (!Z_ISUNDEF(rv)) {
8696
if (Z_TYPE(rv) == IS_STRING) {
8797
key->key = Z_STR(rv);
@@ -125,8 +135,52 @@ static spl_SplObjectStorageElement* spl_object_storage_get(spl_SplObjectStorage
125135
}
126136
} /* }}} */
127137

138+
static spl_SplObjectStorageElement *spl_object_storage_create_element(zend_object *obj, zval *inf) /* {{{ */
139+
{
140+
spl_SplObjectStorageElement *pelement = emalloc(sizeof(spl_SplObjectStorageElement));
141+
pelement->obj = obj;
142+
GC_ADDREF(obj);
143+
if (inf) {
144+
ZVAL_COPY(&pelement->inf, inf);
145+
} else {
146+
ZVAL_NULL(&pelement->inf);
147+
}
148+
return pelement;
149+
} /* }}} */
150+
151+
static spl_SplObjectStorageElement *spl_object_storage_attach_handle(spl_SplObjectStorage *intern, zend_object *obj, zval *inf) /* {{{ */
152+
{
153+
uint32_t handle = obj->handle;
154+
zval *entry_zv;
155+
spl_SplObjectStorageElement *pelement;
156+
entry_zv = zend_hash_index_lookup(&intern->storage, handle);
157+
ZEND_ASSERT(intern->fptrs == NULL || !intern->fptrs->override_write_dimension);
158+
159+
if (Z_TYPE_P(entry_zv) != IS_NULL) {
160+
ZEND_ASSERT(Z_TYPE_P(entry_zv) == IS_PTR);
161+
pelement = Z_PTR_P(entry_zv);
162+
/* FIXME unsafe if destructor of inf moves/removes this entry */
163+
zval_ptr_dtor(&pelement->inf);
164+
if (inf) {
165+
ZVAL_COPY(&pelement->inf, inf);
166+
} else {
167+
ZVAL_NULL(&pelement->inf);
168+
}
169+
return pelement;
170+
}
171+
172+
ZEND_ASSERT((GC_FLAGS(&intern->storage) & IS_ARRAY_PERSISTENT) == 0);
173+
pelement = spl_object_storage_create_element(obj, inf);
174+
ZVAL_PTR(entry_zv, pelement);
175+
return pelement;
176+
} /* }}} */
177+
128178
spl_SplObjectStorageElement *spl_object_storage_attach(spl_SplObjectStorage *intern, zend_object *obj, zval *inf) /* {{{ */
129179
{
180+
if (EXPECTED(intern->fptrs == NULL || !intern->fptrs->override_write_dimension)) {
181+
return spl_object_storage_attach_handle(intern, obj, inf);
182+
}
183+
130184
spl_SplObjectStorageElement *pelement, element;
131185
zend_hash_key key;
132186
if (spl_object_storage_get_hash(&key, intern, obj) == FAILURE) {
@@ -136,6 +190,7 @@ spl_SplObjectStorageElement *spl_object_storage_attach(spl_SplObjectStorage *int
136190
pelement = spl_object_storage_get(intern, &key);
137191

138192
if (pelement) {
193+
/* FIXME unsafe if destructor of inf moves/removes this entry */
139194
zval_ptr_dtor(&pelement->inf);
140195
if (inf) {
141196
ZVAL_COPY(&pelement->inf, inf);
@@ -189,6 +244,9 @@ void spl_object_storage_addall(spl_SplObjectStorage *intern, spl_SplObjectStorag
189244
intern->index = 0;
190245
} /* }}} */
191246

247+
#define SPL_OBJECT_STORAGE_CLASS_HAS_OVERRIDE(class_type, method) \
248+
(((zend_function *)zend_hash_str_find_ptr(&(class_type)->function_table, (method), sizeof((method)) - 1))->common.scope != spl_ce_SplObjectStorage)
249+
192250
static zend_object *spl_object_storage_new_ex(zend_class_entry *class_type, zend_object *orig) /* {{{ */
193251
{
194252
spl_SplObjectStorage *intern;
@@ -207,10 +265,21 @@ static zend_object *spl_object_storage_new_ex(zend_class_entry *class_type, zend
207265

208266
while (parent) {
209267
if (parent == spl_ce_SplObjectStorage) {
268+
/* TODO: Make these singletons with a map from class entry to IS_NULL/IS_PTR */
210269
if (class_type != spl_ce_SplObjectStorage) {
211-
intern->fptr_get_hash = zend_hash_str_find_ptr(&class_type->function_table, "gethash", sizeof("gethash") - 1);
212-
if (intern->fptr_get_hash->common.scope == spl_ce_SplObjectStorage) {
213-
intern->fptr_get_hash = NULL;
270+
spl_SplObjectStorage_fptrs fptrs;
271+
fptrs.get_hash = zend_hash_str_find_ptr(&class_type->function_table, "gethash", sizeof("gethash") - 1);
272+
if (fptrs.get_hash->common.scope == spl_ce_SplObjectStorage) {
273+
fptrs.get_hash = NULL;
274+
}
275+
fptrs.override_read_dimension = fptrs.get_hash != NULL ||
276+
SPL_OBJECT_STORAGE_CLASS_HAS_OVERRIDE(class_type, "offsetget") ||
277+
SPL_OBJECT_STORAGE_CLASS_HAS_OVERRIDE(class_type, "offsetexists");
278+
279+
fptrs.override_write_dimension = fptrs.get_hash != NULL || SPL_OBJECT_STORAGE_CLASS_HAS_OVERRIDE(class_type, "offsetset");
280+
if (fptrs.get_hash || fptrs.override_read_dimension || fptrs.override_write_dimension) {
281+
intern->fptrs = emalloc(sizeof(spl_SplObjectStorage_fptrs));
282+
*intern->fptrs = fptrs;
214283
}
215284
}
216285
break;
@@ -361,6 +430,39 @@ PHP_METHOD(SplObjectStorage, attach)
361430
spl_object_storage_attach(intern, obj, inf);
362431
} /* }}} */
363432

433+
static zval *spl_object_storage_read_dimension(zend_object *object, zval *offset, int type, zval *rv)
434+
{
435+
spl_SplObjectStorage *intern = spl_object_storage_from_obj(object);
436+
if (UNEXPECTED(offset == NULL || Z_TYPE_P(offset) != IS_OBJECT || (intern->fptrs && intern->fptrs->override_read_dimension))) {
437+
/* Can't optimize it if getHash, offsetExists, or offsetGet is overridden */
438+
return zend_std_read_dimension(object, offset, type, rv);
439+
}
440+
spl_SplObjectStorageElement *element = zend_hash_index_find_ptr(&intern->storage, Z_OBJ_HANDLE_P(offset));
441+
if (type == BP_VAR_IS) {
442+
if (element == NULL) {
443+
return &EG(uninitialized_zval);
444+
}
445+
}
446+
447+
if (!element) {
448+
zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0, "Object not found");
449+
return NULL;
450+
} else {
451+
ZVAL_COPY_DEREF(rv, &element->inf);
452+
return rv;
453+
}
454+
}
455+
456+
static void spl_object_storage_write_dimension(zend_object *object, zval *offset, zval *inf)
457+
{
458+
spl_SplObjectStorage *intern = spl_object_storage_from_obj(object);
459+
if (UNEXPECTED(offset == NULL || Z_TYPE_P(offset) != IS_OBJECT || (intern->fptrs && intern->fptrs->override_write_dimension))) {
460+
zend_std_write_dimension(object, offset, inf);
461+
return;
462+
}
463+
spl_object_storage_attach_handle(intern, Z_OBJ_P(offset), inf);
464+
}
465+
364466
/* {{{ Detaches an object from the storage */
365467
PHP_METHOD(SplObjectStorage, detach)
366468
{
@@ -1201,6 +1303,8 @@ PHP_MINIT_FUNCTION(spl_observer)
12011303
spl_handler_SplObjectStorage.clone_obj = spl_object_storage_clone;
12021304
spl_handler_SplObjectStorage.get_gc = spl_object_storage_get_gc;
12031305
spl_handler_SplObjectStorage.free_obj = spl_SplObjectStorage_free_storage;
1306+
spl_handler_SplObjectStorage.read_dimension = spl_object_storage_read_dimension;
1307+
spl_handler_SplObjectStorage.write_dimension = spl_object_storage_write_dimension;
12041308

12051309
spl_ce_MultipleIterator = register_class_MultipleIterator(zend_ce_iterator);
12061310
spl_ce_MultipleIterator->create_object = spl_SplObjectStorage_new;
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
--TEST--
2+
SplObjectStorage magic operators
3+
--FILE--
4+
<?php
5+
$i = 'dynamic';
6+
$v = $i . '_';
7+
$s = new SplObjectStorage();
8+
$o1 = new Stdclass;
9+
$o2 = new Stdclass;
10+
var_dump($s[$o1] ?? 'default');
11+
var_dump($s[$o1] ??= $i);
12+
var_dump($s[$o1] ??= null);
13+
var_dump($s[$o1] ??= false);
14+
var_dump($s[$o1] ?? $i);
15+
$s[$o2] = null;
16+
var_dump(isset($s[$o2]));
17+
var_dump($s[$o2] ?? new stdClass());
18+
var_dump(isset($s[$o2]));
19+
try {
20+
$s['invalid'] = 123;
21+
} catch (Error $e) {
22+
printf("%s: %s\n", $e::class, $e->getMessage());
23+
}
24+
try {
25+
var_dump(isset($s['invalid']));
26+
} catch (Error $e) {
27+
printf("%s: %s\n", $e::class, $e->getMessage());
28+
}
29+
$a = &$s[$o1];
30+
31+
var_dump($s);
32+
33+
?>
34+
--EXPECTF--
35+
string(7) "default"
36+
string(7) "dynamic"
37+
string(7) "dynamic"
38+
string(7) "dynamic"
39+
string(7) "dynamic"
40+
bool(true)
41+
object(stdClass)#4 (0) {
42+
}
43+
bool(true)
44+
TypeError: SplObjectStorage::offsetSet(): Argument #1 ($object) must be of type object, string given
45+
TypeError: SplObjectStorage::offsetExists(): Argument #1 ($object) must be of type object, string given
46+
47+
Notice: Indirect modification of overloaded element of SplObjectStorage has no effect in %s on line 26
48+
object(SplObjectStorage)#1 (1) {
49+
["storage":"SplObjectStorage":private]=>
50+
array(2) {
51+
[0]=>
52+
array(2) {
53+
["obj"]=>
54+
object(stdClass)#2 (0) {
55+
}
56+
["inf"]=>
57+
string(7) "dynamic"
58+
}
59+
[1]=>
60+
array(2) {
61+
["obj"]=>
62+
object(stdClass)#3 (0) {
63+
}
64+
["inf"]=>
65+
NULL
66+
}
67+
}
68+
}

0 commit comments

Comments
 (0)