Skip to content

Commit 2eaf319

Browse files
committed
Implement php_url_encode_to_smart_str() and use it in http_build_query()
This avoids temporary allocations and some copies. For this benchmark: ```php for ($i=0;$i<2000000;$i++) { http_build_query([999999 => 'foo', 'aaab' => 'def', 'aaaaa'=>1, 'aaaaaaaa' => 'a']); } ``` On an i7-4790: ``` Benchmark 1: ./sapi/cli/php ../buildquery.php Time (mean ± σ): 298.9 ms ± 7.3 ms [User: 295.6 ms, System: 2.3 ms] Range (min … max): 293.6 ms … 314.0 ms 10 runs Benchmark 2: ./sapi/cli/php_old ../buildquery.php Time (mean ± σ): 594.8 ms ± 8.6 ms [User: 590.8 ms, System: 2.4 ms] Range (min … max): 586.3 ms … 616.1 ms 10 runs Summary ./sapi/cli/php ../buildquery.php ran 1.99 ± 0.06 times faster than ./sapi/cli/php_old ../buildquery.php ``` For this benchmark: ```php for ($i=0;$i<2000000;$i++) { http_build_query(['test' => 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa']); } ``` On an i7-4790: ``` Benchmark 1: ./sapi/cli/php ../buildquery.php Time (mean ± σ): 188.4 ms ± 6.7 ms [User: 184.6 ms, System: 2.9 ms] Range (min … max): 182.0 ms … 205.4 ms 14 runs Benchmark 2: ./sapi/cli/php_old ../buildquery.php Time (mean ± σ): 323.9 ms ± 8.7 ms [User: 319.8 ms, System: 2.7 ms] Range (min … max): 318.0 ms … 341.2 ms 10 runs Summary ./sapi/cli/php ../buildquery.php ran 1.72 ± 0.08 times faster than ./sapi/cli/php_old ../buildquery.php ```
1 parent 1a23a41 commit 2eaf319

File tree

4 files changed

+30
-38
lines changed

4 files changed

+30
-38
lines changed

UPGRADING.INTERNALS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ PHP 8.5 INTERNALS UPGRADE NOTES
7070
non-ex counterparts do not work in-place.
7171
. The php_std_date() function has been removed. Use php_format_date() with
7272
the "D, d M Y H:i:s \\G\\M\\T" format instead.
73+
. Added php_url_encode_to_smart_str() to encode a URL to a smart_str buffer.
7374

7475
========================
7576
4. OpCode changes

ext/standard/http.c

Lines changed: 4 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -37,14 +37,7 @@ static void php_url_encode_scalar(zval *scalar, smart_str *form_str,
3737
smart_str_append(form_str, key_prefix);
3838
}
3939
if (index_string) {
40-
zend_string *encoded_key;
41-
if (encoding_type == PHP_QUERY_RFC3986) {
42-
encoded_key = php_raw_url_encode(index_string, index_string_len);
43-
} else {
44-
encoded_key = php_url_encode(index_string, index_string_len);
45-
}
46-
smart_str_append(form_str, encoded_key);
47-
zend_string_free(encoded_key);
40+
php_url_encode_to_smart_str(form_str, index_string, index_string_len, encoding_type == PHP_QUERY_RFC3986);
4841
} else {
4942
/* Numeric key */
5043
if (num_prefix) {
@@ -59,31 +52,16 @@ static void php_url_encode_scalar(zval *scalar, smart_str *form_str,
5952

6053
try_again:
6154
switch (Z_TYPE_P(scalar)) {
62-
case IS_STRING: {
63-
zend_string *encoded_data;
64-
if (encoding_type == PHP_QUERY_RFC3986) {
65-
encoded_data = php_raw_url_encode(Z_STRVAL_P(scalar), Z_STRLEN_P(scalar));
66-
} else {
67-
encoded_data = php_url_encode(Z_STRVAL_P(scalar), Z_STRLEN_P(scalar));
68-
}
69-
smart_str_append(form_str, encoded_data);
70-
zend_string_free(encoded_data);
55+
case IS_STRING:
56+
php_url_encode_to_smart_str(form_str, Z_STRVAL_P(scalar), Z_STRLEN_P(scalar), encoding_type == PHP_QUERY_RFC3986);
7157
break;
72-
}
7358
case IS_LONG:
7459
smart_str_append_long(form_str, Z_LVAL_P(scalar));
7560
break;
7661
case IS_DOUBLE: {
77-
zend_string *encoded_data;
7862
zend_string *tmp = zend_double_to_str(Z_DVAL_P(scalar));
79-
if (encoding_type == PHP_QUERY_RFC3986) {
80-
encoded_data = php_raw_url_encode(ZSTR_VAL(tmp), ZSTR_LEN(tmp));
81-
} else {
82-
encoded_data = php_url_encode(ZSTR_VAL(tmp), ZSTR_LEN(tmp));
83-
}
84-
smart_str_append(form_str, encoded_data);
63+
php_url_encode_to_smart_str(form_str, ZSTR_VAL(tmp), ZSTR_LEN(tmp), encoding_type == PHP_QUERY_RFC3986);
8564
zend_string_free(tmp);
86-
zend_string_free(encoded_data);
8765
break;
8866
}
8967
case IS_FALSE:

ext/standard/url.c

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
#include "url.h"
2525
#include "file.h"
2626
#include "zend_simd.h"
27+
#include "Zend/zend_smart_str.h"
2728

2829
/* {{{ free_url */
2930
PHPAPI void php_url_free(php_url *theurl)
@@ -446,16 +447,13 @@ static int php_htoi(const char *s)
446447

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

449-
static zend_always_inline zend_string *php_url_encode_impl(const char *s, size_t len, bool raw) /* {{{ */ {
450+
static zend_always_inline size_t php_url_encode_impl(unsigned char *to, const char *s, size_t len, bool raw) /* {{{ */ {
450451
unsigned char c;
451-
unsigned char *to;
452452
unsigned char const *from, *end;
453-
zend_string *start;
453+
const unsigned char *to_init = to;
454454

455455
from = (unsigned char *)s;
456456
end = (unsigned char *)s + len;
457-
start = zend_string_safe_alloc(3, len, 0, 0);
458-
to = (unsigned char*)ZSTR_VAL(start);
459457

460458
#ifdef XSSE2
461459
while (from + 16 < end) {
@@ -534,19 +532,24 @@ static zend_always_inline zend_string *php_url_encode_impl(const char *s, size_t
534532
*to++ = c;
535533
}
536534
}
537-
*to = '\0';
538535

539-
ZEND_ASSERT(!ZSTR_IS_INTERNED(start) && GC_REFCOUNT(start) == 1);
540-
start = zend_string_truncate(start, to - (unsigned char*)ZSTR_VAL(start), 0);
541-
542-
return start;
536+
return to - to_init;
543537
}
544538
/* }}} */
545539

540+
static zend_always_inline zend_string *php_url_encode_helper(char const *s, size_t len, bool raw)
541+
{
542+
zend_string *result = zend_string_safe_alloc(3, len, 0, false);
543+
size_t length = php_url_encode_impl((unsigned char *) ZSTR_VAL(result), s, len, raw);
544+
ZSTR_VAL(result)[length] = '\0';
545+
ZEND_ASSERT(!ZSTR_IS_INTERNED(result) && GC_REFCOUNT(result) == 1);
546+
return zend_string_truncate(result, length, false);
547+
}
548+
546549
/* {{{ php_url_encode */
547550
PHPAPI zend_string *php_url_encode(char const *s, size_t len)
548551
{
549-
return php_url_encode_impl(s, len, 0);
552+
return php_url_encode_helper(s, len, false);
550553
}
551554
/* }}} */
552555

@@ -613,10 +616,19 @@ PHPAPI size_t php_url_decode(char *str, size_t len)
613616
/* {{{ php_raw_url_encode */
614617
PHPAPI zend_string *php_raw_url_encode(char const *s, size_t len)
615618
{
616-
return php_url_encode_impl(s, len, 1);
619+
return php_url_encode_helper(s, len, true);
617620
}
618621
/* }}} */
619622

623+
PHPAPI void php_url_encode_to_smart_str(smart_str *buf, char const *s, size_t len, bool raw)
624+
{
625+
size_t start_length = smart_str_get_len(buf);
626+
size_t extend = zend_safe_address_guarded(3, len, 0);
627+
char *dest = smart_str_extend(buf, extend);
628+
size_t length = php_url_encode_impl((unsigned char *) dest, s, len, raw);
629+
ZSTR_LEN(buf->s) = start_length + length;
630+
}
631+
620632
/* {{{ URL-encodes string */
621633
PHP_FUNCTION(rawurlencode)
622634
{

ext/standard/url.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ PHPAPI size_t php_raw_url_decode(char *str, size_t len); /* return value: length
3838
PHPAPI size_t php_raw_url_decode_ex(char *dest, const char *src, size_t src_len);
3939
PHPAPI zend_string *php_url_encode(char const *s, size_t len);
4040
PHPAPI zend_string *php_raw_url_encode(char const *s, size_t len);
41+
PHPAPI void php_url_encode_to_smart_str(smart_str *buf, char const *s, size_t len, bool raw);
4142

4243
#define PHP_URL_SCHEME 0
4344
#define PHP_URL_HOST 1

0 commit comments

Comments
 (0)