Skip to content

Commit 4df3dd7

Browse files
authored
Reduce memory allocated by var_export, json_encode, serialize, and other (#8902)
smart_str uses an over-allocated string to optimize for append operations. Functions that use smart_str tend to return the over-allocated string directly. This results in unnecessary memory usage, especially for small strings. The overhead can be up to 231 bytes for strings smaller than that, and 4095 for other strings. This can be avoided for strings smaller than `4096 - zend_string header size - 1` by reallocating the string. This change introduces `smart_str_trim_to_size()`, and calls it in `smart_str_extract()`. Functions that use `smart_str` are updated to use `smart_str_extract()`. Fixes GH-8896
1 parent eacf6f4 commit 4df3dd7

18 files changed

+50
-58
lines changed

NEWS

+7-2
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,16 @@ PHP NEWS
22
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
33
?? ??? ????, PHP 8.2.0beta1
44

5+
- Core:
6+
. Reduced the memory footprint of strings returned by var_export(),
7+
json_encode(), serialize(), iconv_*(), mb_ereg*(), session_create_id(),
8+
http_build_query(), strstr(), Reflection*::__toString(). (Arnaud)
9+
510
- CLI:
6-
- Updated the mime-type table for the builtin-server. (Ayesh Karunaratne)
11+
. Updated the mime-type table for the builtin-server. (Ayesh Karunaratne)
712

813
- FPM:
9-
- Added listen.setfib pool option to set route FIB on FreeBSD. (David Carlier)
14+
. Added listen.setfib pool option to set route FIB on FreeBSD. (David Carlier)
1015

1116
07 Jul 2022, PHP 8.2.0alpha3
1217

UPGRADING.INTERNALS

+7
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,13 @@ PHP 8.2 INTERNALS UPGRADE NOTES
3939
- zend_object_do_operation_t
4040
* Added a new zero_position argument to php_stream_fopen_from_fd_rel to reflect
4141
if this a newly created file so the current file offset needs not to be checked.
42+
* Added smart_str_trim_to_size(). The function trims the memory allocated for the
43+
string. This can considerably reduce the memory footprint of strings smaller
44+
than approximately 4096 bytes.
45+
* smart_str_extract() and the spprintf family of functions now use
46+
smart_str_trim_to_size() before returning the string.
47+
* It is recommended to use smart_str_extract() or smart_str_trim_to_size() when
48+
using the smart_str API.
4249

4350
========================
4451
2. Build system changes

Zend/zend.c

+1-2
Original file line numberDiff line numberDiff line change
@@ -272,8 +272,7 @@ ZEND_API zend_string *zend_vstrpprintf(size_t max_len, const char *format, va_li
272272
ZSTR_LEN(buf.s) = max_len;
273273
}
274274

275-
smart_str_0(&buf);
276-
return buf.s;
275+
return smart_str_extract(&buf);
277276
}
278277
/* }}} */
279278

Zend/zend_smart_str.h

+14-1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@
2525
smart_str_appendl_ex((dest), (src), strlen(src), (what))
2626
#define smart_str_appends(dest, src) \
2727
smart_str_appendl((dest), (src), strlen(src))
28+
#define smart_str_extract(dest) \
29+
smart_str_extract_ex((dest), 0)
30+
#define smart_str_trim_to_size(dest) \
31+
smart_str_trim_to_size_ex((dest), 0)
2832
#define smart_str_extend(dest, len) \
2933
smart_str_extend_ex((dest), (len), 0)
3034
#define smart_str_appendc(dest, c) \
@@ -101,10 +105,19 @@ static zend_always_inline size_t smart_str_get_len(smart_str *str) {
101105
return str->s ? ZSTR_LEN(str->s) : 0;
102106
}
103107

104-
static zend_always_inline zend_string *smart_str_extract(smart_str *str) {
108+
static zend_always_inline void smart_str_trim_to_size_ex(smart_str *str, bool persistent)
109+
{
110+
if (str->s && str->a > ZSTR_LEN(str->s)) {
111+
str->s = zend_string_realloc(str->s, ZSTR_LEN(str->s), persistent);
112+
str->a = ZSTR_LEN(str->s);
113+
}
114+
}
115+
116+
static zend_always_inline zend_string *smart_str_extract_ex(smart_str *str, bool persistent) {
105117
if (str->s) {
106118
zend_string *res;
107119
smart_str_0(str);
120+
smart_str_trim_to_size_ex(str, persistent);
108121
res = str->s;
109122
str->s = NULL;
110123
return res;

Zend/zend_smart_str_public.h

+1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#define ZEND_SMART_STR_PUBLIC_H
1919

2020
typedef struct {
21+
/** See smart_str_extract() */
2122
zend_string *s;
2223
size_t a;
2324
} smart_str;

ext/dom/documenttype.c

+1-2
Original file line numberDiff line numberDiff line change
@@ -188,8 +188,7 @@ int dom_documenttype_internal_subset_read(dom_object *obj, zval *retval)
188188
}
189189

190190
if (ret_buf.s) {
191-
smart_str_0(&ret_buf);
192-
ZVAL_NEW_STR(retval, ret_buf.s);
191+
ZVAL_STR(retval, smart_str_extract(&ret_buf));
193192
return SUCCESS;
194193
}
195194
}

ext/filter/sanitizing_filters.c

+1-2
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,8 @@ static void php_filter_encode_html(zval *value, const unsigned char *chars)
4646
s++;
4747
}
4848

49-
smart_str_0(&str);
5049
zval_ptr_dtor(value);
51-
ZVAL_NEW_STR(value, str.s);
50+
ZVAL_STR(value, smart_str_extract(&str));
5251
}
5352

5453
static const unsigned char hexchars[] = "0123456789ABCDEF";

ext/iconv/iconv.c

+3-11
Original file line numberDiff line numberDiff line change
@@ -1845,7 +1845,7 @@ PHP_FUNCTION(iconv_substr)
18451845
_php_iconv_show_error(err, GENERIC_SUPERSET_NAME, charset);
18461846

18471847
if (err == PHP_ICONV_ERR_SUCCESS && retval.s != NULL) {
1848-
RETURN_NEW_STR(retval.s);
1848+
RETURN_STR(smart_str_extract(&retval));
18491849
}
18501850
smart_str_free(&retval);
18511851
RETURN_FALSE;
@@ -2038,11 +2038,7 @@ PHP_FUNCTION(iconv_mime_encode)
20382038
_php_iconv_show_error(err, out_charset, in_charset);
20392039

20402040
if (err == PHP_ICONV_ERR_SUCCESS) {
2041-
if (retval.s != NULL) {
2042-
RETVAL_STR(retval.s);
2043-
} else {
2044-
RETVAL_EMPTY_STRING();
2045-
}
2041+
RETVAL_STR(smart_str_extract(&retval));
20462042
} else {
20472043
smart_str_free(&retval);
20482044
RETVAL_FALSE;
@@ -2083,11 +2079,7 @@ PHP_FUNCTION(iconv_mime_decode)
20832079
_php_iconv_show_error(err, charset, "???");
20842080

20852081
if (err == PHP_ICONV_ERR_SUCCESS) {
2086-
if (retval.s != NULL) {
2087-
RETVAL_STR(retval.s);
2088-
} else {
2089-
RETVAL_EMPTY_STRING();
2090-
}
2082+
RETVAL_STR(smart_str_extract(&retval));
20912083
} else {
20922084
smart_str_free(&retval);
20932085
RETVAL_FALSE;

ext/json/json.c

+1-5
Original file line numberDiff line numberDiff line change
@@ -212,11 +212,7 @@ PHP_FUNCTION(json_encode)
212212
}
213213
}
214214

215-
smart_str_0(&buf); /* copy? */
216-
if (buf.s) {
217-
RETURN_NEW_STR(buf.s);
218-
}
219-
RETURN_EMPTY_STRING();
215+
RETURN_STR(smart_str_extract(&buf));
220216
}
221217
/* }}} */
222218

ext/mbstring/php_mbregex.c

+3-6
Original file line numberDiff line numberDiff line change
@@ -1144,13 +1144,10 @@ static void _php_mb_regex_ereg_replace_exec(INTERNAL_FUNCTION_PARAMETERS, OnigOp
11441144

11451145
if (err <= -2) {
11461146
smart_str_free(&out_buf);
1147-
RETVAL_FALSE;
1148-
} else if (out_buf.s) {
1149-
smart_str_0(&out_buf);
1150-
RETVAL_STR(out_buf.s);
1151-
} else {
1152-
RETVAL_EMPTY_STRING();
1147+
RETURN_FALSE;
11531148
}
1149+
1150+
RETURN_STR(smart_str_extract(&out_buf));
11541151
}
11551152
/* }}} */
11561153

ext/session/session.c

+2-3
Original file line numberDiff line numberDiff line change
@@ -1464,7 +1464,7 @@ PHPAPI zend_result php_session_reset_id(void) /* {{{ */
14641464
smart_str_0(&var);
14651465
if (sid) {
14661466
zval_ptr_dtor_str(sid);
1467-
ZVAL_NEW_STR(sid, var.s);
1467+
ZVAL_STR(sid, smart_str_extract(&var));
14681468
} else {
14691469
REGISTER_STRINGL_CONSTANT("SID", ZSTR_VAL(var.s), ZSTR_LEN(var.s), 0);
14701470
smart_str_free(&var);
@@ -2362,8 +2362,7 @@ PHP_FUNCTION(session_create_id)
23622362
php_error_docref(NULL, E_WARNING, "Failed to create new ID");
23632363
RETURN_FALSE;
23642364
}
2365-
smart_str_0(&id);
2366-
RETVAL_NEW_STR(id.s);
2365+
RETVAL_STR(smart_str_extract(&id));
23672366
}
23682367
/* }}} */
23692368

ext/soap/php_sdl.c

+2-4
Original file line numberDiff line numberDiff line change
@@ -3261,8 +3261,7 @@ sdlPtr get_sdl(zval *this_ptr, char *uri, zend_long cache_wsdl)
32613261
smart_str_appends(&proxy,Z_STRVAL_P(proxy_host));
32623262
smart_str_appends(&proxy,":");
32633263
smart_str_append_long(&proxy,Z_LVAL_P(proxy_port));
3264-
smart_str_0(&proxy);
3265-
ZVAL_NEW_STR(&str_proxy, proxy.s);
3264+
ZVAL_STR(&str_proxy, smart_str_extract(&proxy));
32663265

32673266
if (!context) {
32683267
context = php_stream_context_alloc();
@@ -3304,8 +3303,7 @@ sdlPtr get_sdl(zval *this_ptr, char *uri, zend_long cache_wsdl)
33043303
http_context_headers(context, has_authorization, has_proxy_authorization, 0, &headers);
33053304
}
33063305

3307-
smart_str_0(&headers);
3308-
ZVAL_NEW_STR(&str_headers, headers.s);
3306+
ZVAL_STR(&str_headers, smart_str_extract(&headers));
33093307
php_stream_context_set_option(context, "http", "header", &str_headers);
33103308
zval_ptr_dtor(&str_headers);
33113309
}

ext/spl/spl_array.c

+1-1
Original file line numberDiff line numberDiff line change
@@ -1554,7 +1554,7 @@ PHP_METHOD(ArrayObject, serialize)
15541554
/* done */
15551555
PHP_VAR_SERIALIZE_DESTROY(var_hash);
15561556

1557-
RETURN_NEW_STR(buf.s);
1557+
RETURN_STR(smart_str_extract(&buf));
15581558
} /* }}} */
15591559

15601560
/* {{{ unserialize the object */

ext/spl/spl_dllist.c

+1-3
Original file line numberDiff line numberDiff line change
@@ -1032,12 +1032,10 @@ PHP_METHOD(SplDoublyLinkedList, serialize)
10321032
current = next;
10331033
}
10341034

1035-
smart_str_0(&buf);
1036-
10371035
/* done */
10381036
PHP_VAR_SERIALIZE_DESTROY(var_hash);
10391037

1040-
RETURN_NEW_STR(buf.s);
1038+
RETURN_STR(smart_str_extract(&buf));
10411039
} /* }}} */
10421040

10431041
/* {{{ Unserializes storage */

ext/spl/spl_observer.c

+1-1
Original file line numberDiff line numberDiff line change
@@ -808,7 +808,7 @@ PHP_METHOD(SplObjectStorage, serialize)
808808
/* done */
809809
PHP_VAR_SERIALIZE_DESTROY(var_hash);
810810

811-
RETURN_NEW_STR(buf.s);
811+
RETURN_STR(smart_str_extract(&buf));
812812
} /* }}} */
813813

814814
/* {{{ Unserializes storage */

ext/standard/http.c

+1-7
Original file line numberDiff line numberDiff line change
@@ -238,12 +238,6 @@ PHP_FUNCTION(http_build_query)
238238

239239
php_url_encode_hash_ex(HASH_OF(formdata), &formstr, prefix, prefix_len, NULL, 0, NULL, 0, (Z_TYPE_P(formdata) == IS_OBJECT ? formdata : NULL), arg_sep, (int)enc_type);
240240

241-
if (!formstr.s) {
242-
RETURN_EMPTY_STRING();
243-
}
244-
245-
smart_str_0(&formstr);
246-
247-
RETURN_NEW_STR(formstr.s);
241+
RETURN_STR(smart_str_extract(&formstr));
248242
}
249243
/* }}} */

ext/standard/string.c

+1-2
Original file line numberDiff line numberDiff line change
@@ -2891,8 +2891,7 @@ static void php_strtr_array(zval *return_value, zend_string *input, HashTable *p
28912891

28922892
if (result.s) {
28932893
smart_str_appendl(&result, str + old_pos, slen - old_pos);
2894-
smart_str_0(&result);
2895-
RETVAL_NEW_STR(result.s);
2894+
RETVAL_STR(smart_str_extract(&result));
28962895
} else {
28972896
smart_str_free(&result);
28982897
RETVAL_STR_COPY(input);

ext/standard/var.c

+2-6
Original file line numberDiff line numberDiff line change
@@ -645,7 +645,7 @@ PHP_FUNCTION(var_export)
645645
smart_str_0 (&buf);
646646

647647
if (return_output) {
648-
RETURN_NEW_STR(buf.s);
648+
RETURN_STR(smart_str_extract(&buf));
649649
} else {
650650
PHPWRITE(ZSTR_VAL(buf.s), ZSTR_LEN(buf.s));
651651
smart_str_free(&buf);
@@ -1318,11 +1318,7 @@ PHP_FUNCTION(serialize)
13181318
RETURN_THROWS();
13191319
}
13201320

1321-
if (buf.s) {
1322-
RETURN_NEW_STR(buf.s);
1323-
} else {
1324-
RETURN_EMPTY_STRING();
1325-
}
1321+
RETURN_STR(smart_str_extract(&buf));
13261322
}
13271323
/* }}} */
13281324

0 commit comments

Comments
 (0)