Skip to content

Commit 876baa4

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 1ec7bb3 commit 876baa4

File tree

4 files changed

+30
-38
lines changed

4 files changed

+30
-38
lines changed

UPGRADING.INTERNALS

+1
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ PHP 8.5 INTERNALS UPGRADE NOTES
6868
- ext/standard
6969
. Added php_url_decode_ex() and php_raw_url_decode_ex() that unlike their
7070
non-ex counterparts do not work in-place.
71+
. Added php_url_encode_to_smart_str() to encode a URL to a smart_str buffer.
7172

7273
========================
7374
4. OpCode changes

ext/standard/http.c

+4-26
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

+24-12
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727

2828
#include "url.h"
2929
#include "file.h"
30+
#include "Zend/zend_smart_str.h"
3031

3132
/* {{{ free_url */
3233
PHPAPI void php_url_free(php_url *theurl)
@@ -449,16 +450,13 @@ static int php_htoi(const char *s)
449450

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

452-
static zend_always_inline zend_string *php_url_encode_impl(const char *s, size_t len, bool raw) /* {{{ */ {
453+
static zend_always_inline size_t php_url_encode_impl(unsigned char *to, const char *s, size_t len, bool raw) /* {{{ */ {
453454
unsigned char c;
454-
unsigned char *to;
455455
unsigned char const *from, *end;
456-
zend_string *start;
456+
const unsigned char *to_init = to;
457457

458458
from = (unsigned char *)s;
459459
end = (unsigned char *)s + len;
460-
start = zend_string_safe_alloc(3, len, 0, 0);
461-
to = (unsigned char*)ZSTR_VAL(start);
462460

463461
#ifdef __SSE2__
464462
while (from + 16 < end) {
@@ -537,19 +535,24 @@ static zend_always_inline zend_string *php_url_encode_impl(const char *s, size_t
537535
*to++ = c;
538536
}
539537
}
540-
*to = '\0';
541538

542-
ZEND_ASSERT(!ZSTR_IS_INTERNED(start) && GC_REFCOUNT(start) == 1);
543-
start = zend_string_truncate(start, to - (unsigned char*)ZSTR_VAL(start), 0);
544-
545-
return start;
539+
return to - to_init;
546540
}
547541
/* }}} */
548542

543+
static zend_always_inline zend_string *php_url_encode_helper(char const *s, size_t len, bool raw)
544+
{
545+
zend_string *result = zend_string_safe_alloc(3, len, 0, false);
546+
size_t length = php_url_encode_impl((unsigned char *) ZSTR_VAL(result), s, len, raw);
547+
ZSTR_VAL(result)[length] = '\0';
548+
ZEND_ASSERT(!ZSTR_IS_INTERNED(result) && GC_REFCOUNT(result) == 1);
549+
return zend_string_truncate(result, length, false);
550+
}
551+
549552
/* {{{ php_url_encode */
550553
PHPAPI zend_string *php_url_encode(char const *s, size_t len)
551554
{
552-
return php_url_encode_impl(s, len, 0);
555+
return php_url_encode_helper(s, len, false);
553556
}
554557
/* }}} */
555558

@@ -616,10 +619,19 @@ PHPAPI size_t php_url_decode(char *str, size_t len)
616619
/* {{{ php_raw_url_encode */
617620
PHPAPI zend_string *php_raw_url_encode(char const *s, size_t len)
618621
{
619-
return php_url_encode_impl(s, len, 1);
622+
return php_url_encode_helper(s, len, true);
620623
}
621624
/* }}} */
622625

626+
PHPAPI void php_url_encode_to_smart_str(smart_str *buf, char const *s, size_t len, bool raw)
627+
{
628+
size_t start_length = smart_str_get_len(buf);
629+
size_t extend = zend_safe_address_guarded(3, len, 0);
630+
char *dest = smart_str_extend(buf, extend);
631+
size_t length = php_url_encode_impl((unsigned char *) dest, s, len, raw);
632+
ZSTR_LEN(buf->s) = start_length + length;
633+
}
634+
623635
/* {{{ URL-encodes string */
624636
PHP_FUNCTION(rawurlencode)
625637
{

ext/standard/url.h

+1
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)