1
0
mirror of https://github.com/php/php-src.git synced 2026-03-24 00:02:20 +01:00

Improve performance of unpack() with nameless repetitions (#18803)

We can avoid creating temporary strings, and then reparsing them into
numbers with zend_symtable_update() by using zend_hash_index_update()
directly.

For the following benchmark on an i7-4790:
```php
$file = str_repeat('A', 100000);
for ($i=0;$i<100;$i++) unpack('C*',$file);
```

I get:
```
Benchmark 1: ./sapi/cli/php y.php
  Time (mean ± σ):      85.8 ms ±   1.8 ms    [User: 74.5 ms, System: 10.4 ms]
  Range (min … max):    83.8 ms …  92.4 ms    33 runs

Benchmark 2: ./sapi/cli/php_old y.php
  Time (mean ± σ):     318.3 ms ±   2.7 ms    [User: 306.7 ms, System: 9.9 ms]
  Range (min … max):   314.9 ms … 321.6 ms    10 runs

Summary
  ./sapi/cli/php y.php ran
    3.71 ± 0.08 times faster than ./sapi/cli/php_old y.php
```

On an i7-1185G7 I get:
```
Benchmark 1: ./sapi/cli/php test.php
  Time (mean ± σ):      60.1 ms ±   0.7 ms    [User: 47.8 ms, System: 12.0 ms]
  Range (min … max):    59.2 ms …  63.8 ms    48 runs

  Warning: Statistical outliers were detected. Consider re-running this benchmark on a quiet system without any interferences from other programs. It might help to use the '--warmup' or '--prepare' options.

Benchmark 2: ./sapi/cli/php_old test.php
  Time (mean ± σ):     220.8 ms ±   2.2 ms    [User: 209.6 ms, System: 10.7 ms]
  Range (min … max):   218.5 ms … 224.5 ms    13 runs

Summary
  ./sapi/cli/php test.php  ran
    3.67 ± 0.06 times faster than ./sapi/cli/php_old test.php
```
This commit is contained in:
Niels Dossche
2025-06-10 22:33:33 +02:00
committed by GitHub
parent 0a95b2f30c
commit 559858c822
2 changed files with 23 additions and 19 deletions

View File

@@ -582,6 +582,8 @@ PHP 8.5 UPGRADE NOTES
. Improved performance of array functions with callbacks
(array_find, array_filter, array_map, usort, ...).
. Improved performance of urlencode() and rawurlencode().
. Improved unpack() performance with nameless repetitions by avoiding
creating temporary strings and reparsing them.
- XMLReader:
. Improved property access performance.

View File

@@ -885,12 +885,15 @@ PHP_FUNCTION(unpack)
if ((inputpos + size) <= inputlen) {
zend_string* real_name;
zend_long long_key = 0;
zval val;
if (repetitions == 1 && namelen > 0) {
if (namelen == 0) {
real_name = NULL;
long_key = i + 1;
} else if (repetitions == 1) {
/* Use a part of the formatarg argument directly as the name. */
real_name = zend_string_init_fast(name, namelen);
} else {
/* Need to add the 1-based element number to the name */
char buf[MAX_LENGTH_OF_LONG + 1];
@@ -912,7 +915,6 @@ PHP_FUNCTION(unpack)
size = len;
ZVAL_STRINGL(&val, &input[inputpos], len);
zend_symtable_update(Z_ARRVAL_P(return_value), real_name, &val);
break;
}
case 'A': {
@@ -939,7 +941,6 @@ PHP_FUNCTION(unpack)
}
ZVAL_STRINGL(&val, &input[inputpos], len + 1);
zend_symtable_update(Z_ARRVAL_P(return_value), real_name, &val);
break;
}
/* New option added for Z to remain in-line with the Perl implementation */
@@ -964,7 +965,6 @@ PHP_FUNCTION(unpack)
len = s;
ZVAL_STRINGL(&val, &input[inputpos], len);
zend_symtable_update(Z_ARRVAL_P(return_value), real_name, &val);
break;
}
@@ -979,7 +979,9 @@ PHP_FUNCTION(unpack)
if (size > INT_MAX / 2) {
zend_string_release(real_name);
if (real_name) {
zend_string_release_ex(real_name, false);
}
zend_argument_value_error(1, "repeater must be less than or equal to %d", INT_MAX / 2);
RETURN_THROWS();
}
@@ -1016,7 +1018,6 @@ PHP_FUNCTION(unpack)
ZSTR_VAL(buf)[len] = '\0';
ZVAL_STR(&val, buf);
zend_symtable_update(Z_ARRVAL_P(return_value), real_name, &val);
break;
}
@@ -1026,7 +1027,6 @@ PHP_FUNCTION(unpack)
zend_long v = (type == 'c') ? (int8_t) x : x;
ZVAL_LONG(&val, v);
zend_symtable_update(Z_ARRVAL_P(return_value), real_name, &val);
break;
}
@@ -1046,7 +1046,6 @@ PHP_FUNCTION(unpack)
}
ZVAL_LONG(&val, v);
zend_symtable_update(Z_ARRVAL_P(return_value), real_name, &val);
break;
}
@@ -1062,7 +1061,6 @@ PHP_FUNCTION(unpack)
}
ZVAL_LONG(&val, v);
zend_symtable_update(Z_ARRVAL_P(return_value), real_name, &val);
break;
}
@@ -1082,8 +1080,6 @@ PHP_FUNCTION(unpack)
}
ZVAL_LONG(&val, v);
zend_symtable_update(Z_ARRVAL_P(return_value), real_name, &val);
break;
}
@@ -1104,7 +1100,6 @@ PHP_FUNCTION(unpack)
}
ZVAL_LONG(&val, v);
zend_symtable_update(Z_ARRVAL_P(return_value), real_name, &val);
break;
}
#endif
@@ -1124,7 +1119,6 @@ PHP_FUNCTION(unpack)
}
ZVAL_DOUBLE(&val, v);
zend_symtable_update(Z_ARRVAL_P(return_value), real_name, &val);
break;
}
@@ -1143,13 +1137,12 @@ PHP_FUNCTION(unpack)
}
ZVAL_DOUBLE(&val, v);
zend_symtable_update(Z_ARRVAL_P(return_value), real_name, &val);
break;
}
case 'x':
/* Do nothing with input, just skip it */
break;
goto no_output;
case 'X':
if (inputpos < size) {
@@ -1160,7 +1153,7 @@ PHP_FUNCTION(unpack)
php_error_docref(NULL, E_WARNING, "Type %c: outside of string", type);
}
}
break;
goto no_output;
case '@':
if (repetitions <= inputlen) {
@@ -1170,10 +1163,19 @@ PHP_FUNCTION(unpack)
}
i = repetitions - 1; /* Done, break out of for loop */
break;
goto no_output;
}
zend_string_release(real_name);
if (real_name) {
zend_symtable_update(Z_ARRVAL_P(return_value), real_name, &val);
} else {
zend_hash_index_update(Z_ARRVAL_P(return_value), long_key, &val);
}
no_output:
if (real_name) {
zend_string_release_ex(real_name, false);
}
inputpos += size;
if (inputpos < 0) {