@@ -71,7 +71,46 @@ build_parsed_json_cust(simdjson::dom::parser& parser, simdjson::dom::element &do
7171 return simdjson::SUCCESS;
7272}
7373
74- /* }}} */
74+ static zend_always_inline void simdjson_set_zval_to_string (zval *v, const char *buf, size_t len) {
75+ /* In php 7.1, the ZSTR_CHAR macro doesn't exist, and CG(one_char_string)[chr] may or may not be null */
76+ #if PHP_VERSION_ID >= 70200
77+ if (len <= 1 ) {
78+ /*
79+ A note on performance benefits of the use of interned strings here and elsewhere:
80+
81+ - PHP doesn't need to allocate a temporary string and initialize it
82+ - PHP doesn't need to free the temporary string
83+ - PHP doesn't need to compute the hash of the temporary string
84+ - Memory usage is reduced because the string representation is reused
85+ - String comparisons are faster when the strings are the exact same pointer.
86+ - CPU caches may already have this interned string
87+ - If all array keys are interned strings, then php can skip the step of
88+ freeing array keys when garbage collecting the array.
89+ */
90+ zend_string *key = len == 1 ? ZSTR_CHAR (buf[0 ]) : ZSTR_EMPTY_ALLOC ();
91+ ZVAL_INTERNED_STR (v, key);
92+ return ;
93+ }
94+ #endif
95+ ZVAL_STRINGL (v, buf, len);
96+ }
97+
98+ static zend_always_inline void simdjson_add_key_to_symtable (HashTable *ht, const char *buf, size_t len, zval *value) {
99+ #if PHP_VERSION_ID >= 70200
100+ if (len <= 1 ) {
101+ /* Look up the interned string (i.e. not reference counted) */
102+ zend_string *key = len == 1 ? ZSTR_CHAR (buf[0 ]) : ZSTR_EMPTY_ALLOC ();
103+ /* Add the key or update the existing value of the key. */
104+ zend_symtable_update (ht, key, value);
105+ /* zend_string_release_ex is a no-op for interned strings */
106+ return ;
107+ }
108+ #endif
109+ zend_string *key = zend_string_init (buf, len, 0 );
110+ zend_symtable_update (ht, key, value);
111+ /* Release the reference counted key */
112+ zend_string_release_ex (key, 0 );
113+ }
75114
76115static zend_always_inline void simdjson_set_zval_to_int64 (zval *zv, const int64_t value) {
77116#if SIZEOF_ZEND_LONG < 8
@@ -89,7 +128,7 @@ static zval create_array(simdjson::dom::element element) /* {{{ */ {
89128 switch (element.type ()) {
90129 // ASCII sort
91130 case simdjson::dom::element_type::STRING :
92- ZVAL_STRINGL (&v, element.get_c_str ().value_unsafe (), element.get_string_length ().value_unsafe ());
131+ simdjson_set_zval_to_string (&v, element.get_c_str ().value_unsafe (), element.get_string_length ().value_unsafe ());
93132 break ;
94133 case simdjson::dom::element_type::INT64 :
95134 simdjson_set_zval_to_int64 (&v, element.get_int64 ().value_unsafe ());
@@ -141,9 +180,7 @@ static zval create_array(simdjson::dom::element element) /* {{{ */ {
141180 for (simdjson::dom::key_value_pair field : json_object) {
142181 zval value = create_array (field.value );
143182 /* TODO consider using zend_string_init_existing_interned in php 8.1+ to save memory and time freeing strings. */
144- zend_string *key = zend_string_init (field.key .data (), field.key .size (), 0 );
145- zend_symtable_update (arr, key, &value);
146- zend_string_release_ex (key, 0 );
183+ simdjson_add_key_to_symtable (arr, field.key .data (), field.key .size (), &value);
147184 }
148185 break ;
149186 }
@@ -161,7 +198,7 @@ static zval create_object(simdjson::dom::element element) /* {{{ */ {
161198 switch (element.type ()) {
162199 // ASCII sort
163200 case simdjson::dom::element_type::STRING :
164- ZVAL_STRINGL (&v, element.get_c_str ().value_unsafe (), element.get_string_length ().value_unsafe ());
201+ simdjson_set_zval_to_string (&v, element.get_c_str ().value_unsafe (), element.get_string_length ().value_unsafe ());
165202 break ;
166203 case simdjson::dom::element_type::INT64 :
167204 simdjson_set_zval_to_int64 (&v, element.get_int64 ().value_unsafe ());
@@ -214,14 +251,36 @@ static zval create_object(simdjson::dom::element element) /* {{{ */ {
214251 return v;
215252 }
216253 zval value = create_object (field.value );
254+
255+ /* Add the key to the object */
217256#if PHP_VERSION_ID >= 80000
218- /* TODO consider using zend_string_init_existing_interned in php 8.1+ to save memory and time freeing strings. */
219- zend_string *key = zend_string_init (data, size, 0 );
220- obj->handlers ->write_property (obj, key, &value, NULL );
257+ zend_string *key;
258+ if (size <= 1 ) {
259+ key = size == 1 ? ZSTR_CHAR (data[0 ]) : ZSTR_EMPTY_ALLOC ();
260+ } else {
261+ key = zend_string_init (data, size, 0 );
262+ }
263+ zend_std_write_property (obj, key, &value, NULL );
221264 zend_string_release_ex (key, 0 );
222265#else
223- add_property_zval_ex (&v, data, size, &value);
266+
267+ # if PHP_VERSION_ID >= 70200
268+ if (size <= 1 ) {
269+ zval zkey;
270+ zend_string *key = size == 1 ? ZSTR_CHAR (data[0 ]) : ZSTR_EMPTY_ALLOC ();
271+ ZVAL_INTERNED_STR (&zkey, key);
272+ zend_std_write_property (&v, &zkey, &value, NULL );
273+ } else
274+ # endif
275+ {
276+ zval zkey;
277+ ZVAL_STRINGL (&zkey, data, size);
278+ zend_std_write_property (&v, &zkey, &value, NULL );
279+ zval_ptr_dtor_nogc (&zkey);
280+ }
224281#endif
282+ /* After the key is added to the object (incrementing the reference count) ,
283+ * decrement the reference count of the value by one */
225284 zval_ptr_dtor_nogc (&value);
226285 }
227286 break ;
0 commit comments