Skip to content

Commit 559858c

Browse files
authored
Improve performance of unpack() with nameless repetitions (php#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 ```
1 parent 0a95b2f commit 559858c

File tree

2 files changed

+23
-19
lines changed

2 files changed

+23
-19
lines changed

UPGRADING

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -582,6 +582,8 @@ PHP 8.5 UPGRADE NOTES
582582
. Improved performance of array functions with callbacks
583583
(array_find, array_filter, array_map, usort, ...).
584584
. Improved performance of urlencode() and rawurlencode().
585+
. Improved unpack() performance with nameless repetitions by avoiding
586+
creating temporary strings and reparsing them.
585587

586588
- XMLReader:
587589
. Improved property access performance.

ext/standard/pack.c

Lines changed: 21 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -885,12 +885,15 @@ PHP_FUNCTION(unpack)
885885
if ((inputpos + size) <= inputlen) {
886886

887887
zend_string* real_name;
888+
zend_long long_key = 0;
888889
zval val;
889890

890-
if (repetitions == 1 && namelen > 0) {
891+
if (namelen == 0) {
892+
real_name = NULL;
893+
long_key = i + 1;
894+
} else if (repetitions == 1) {
891895
/* Use a part of the formatarg argument directly as the name. */
892896
real_name = zend_string_init_fast(name, namelen);
893-
894897
} else {
895898
/* Need to add the 1-based element number to the name */
896899
char buf[MAX_LENGTH_OF_LONG + 1];
@@ -912,7 +915,6 @@ PHP_FUNCTION(unpack)
912915
size = len;
913916

914917
ZVAL_STRINGL(&val, &input[inputpos], len);
915-
zend_symtable_update(Z_ARRVAL_P(return_value), real_name, &val);
916918
break;
917919
}
918920
case 'A': {
@@ -939,7 +941,6 @@ PHP_FUNCTION(unpack)
939941
}
940942

941943
ZVAL_STRINGL(&val, &input[inputpos], len + 1);
942-
zend_symtable_update(Z_ARRVAL_P(return_value), real_name, &val);
943944
break;
944945
}
945946
/* New option added for Z to remain in-line with the Perl implementation */
@@ -964,7 +965,6 @@ PHP_FUNCTION(unpack)
964965
len = s;
965966

966967
ZVAL_STRINGL(&val, &input[inputpos], len);
967-
zend_symtable_update(Z_ARRVAL_P(return_value), real_name, &val);
968968
break;
969969
}
970970

@@ -979,7 +979,9 @@ PHP_FUNCTION(unpack)
979979

980980

981981
if (size > INT_MAX / 2) {
982-
zend_string_release(real_name);
982+
if (real_name) {
983+
zend_string_release_ex(real_name, false);
984+
}
983985
zend_argument_value_error(1, "repeater must be less than or equal to %d", INT_MAX / 2);
984986
RETURN_THROWS();
985987
}
@@ -1016,7 +1018,6 @@ PHP_FUNCTION(unpack)
10161018
ZSTR_VAL(buf)[len] = '\0';
10171019

10181020
ZVAL_STR(&val, buf);
1019-
zend_symtable_update(Z_ARRVAL_P(return_value), real_name, &val);
10201021
break;
10211022
}
10221023

@@ -1026,7 +1027,6 @@ PHP_FUNCTION(unpack)
10261027
zend_long v = (type == 'c') ? (int8_t) x : x;
10271028

10281029
ZVAL_LONG(&val, v);
1029-
zend_symtable_update(Z_ARRVAL_P(return_value), real_name, &val);
10301030
break;
10311031
}
10321032

@@ -1046,7 +1046,6 @@ PHP_FUNCTION(unpack)
10461046
}
10471047

10481048
ZVAL_LONG(&val, v);
1049-
zend_symtable_update(Z_ARRVAL_P(return_value), real_name, &val);
10501049
break;
10511050
}
10521051

@@ -1062,7 +1061,6 @@ PHP_FUNCTION(unpack)
10621061
}
10631062

10641063
ZVAL_LONG(&val, v);
1065-
zend_symtable_update(Z_ARRVAL_P(return_value), real_name, &val);
10661064
break;
10671065
}
10681066

@@ -1082,8 +1080,6 @@ PHP_FUNCTION(unpack)
10821080
}
10831081

10841082
ZVAL_LONG(&val, v);
1085-
zend_symtable_update(Z_ARRVAL_P(return_value), real_name, &val);
1086-
10871083
break;
10881084
}
10891085

@@ -1104,7 +1100,6 @@ PHP_FUNCTION(unpack)
11041100
}
11051101

11061102
ZVAL_LONG(&val, v);
1107-
zend_symtable_update(Z_ARRVAL_P(return_value), real_name, &val);
11081103
break;
11091104
}
11101105
#endif
@@ -1124,7 +1119,6 @@ PHP_FUNCTION(unpack)
11241119
}
11251120

11261121
ZVAL_DOUBLE(&val, v);
1127-
zend_symtable_update(Z_ARRVAL_P(return_value), real_name, &val);
11281122
break;
11291123
}
11301124

@@ -1143,13 +1137,12 @@ PHP_FUNCTION(unpack)
11431137
}
11441138

11451139
ZVAL_DOUBLE(&val, v);
1146-
zend_symtable_update(Z_ARRVAL_P(return_value), real_name, &val);
11471140
break;
11481141
}
11491142

11501143
case 'x':
11511144
/* Do nothing with input, just skip it */
1152-
break;
1145+
goto no_output;
11531146

11541147
case 'X':
11551148
if (inputpos < size) {
@@ -1160,7 +1153,7 @@ PHP_FUNCTION(unpack)
11601153
php_error_docref(NULL, E_WARNING, "Type %c: outside of string", type);
11611154
}
11621155
}
1163-
break;
1156+
goto no_output;
11641157

11651158
case '@':
11661159
if (repetitions <= inputlen) {
@@ -1170,10 +1163,19 @@ PHP_FUNCTION(unpack)
11701163
}
11711164

11721165
i = repetitions - 1; /* Done, break out of for loop */
1173-
break;
1166+
goto no_output;
11741167
}
11751168

1176-
zend_string_release(real_name);
1169+
if (real_name) {
1170+
zend_symtable_update(Z_ARRVAL_P(return_value), real_name, &val);
1171+
} else {
1172+
zend_hash_index_update(Z_ARRVAL_P(return_value), long_key, &val);
1173+
}
1174+
1175+
no_output:
1176+
if (real_name) {
1177+
zend_string_release_ex(real_name, false);
1178+
}
11771179

11781180
inputpos += size;
11791181
if (inputpos < 0) {

0 commit comments

Comments
 (0)