From 836cda6e0febff21fafec2abacd28d790cd5e480 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Thu, 8 May 2025 23:13:32 +0200 Subject: [PATCH 1/3] Optimize pack() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Instead of using lookup tables, we can use a combination of shifts and byte swapping to achieve the same thing in less cycles and with less code. Benchmark files --------------- pack1.php: ```php for ($i = 0; $i < 10_000_000; ++$i) { pack("J", 0x7FFFFFFFFFFFFFFF); } ``` pack2.php: ```php for ($i = 0; $i < 4000000; ++$i) { pack("nvc*", 0x1234, 0x5678, 65, 66); } ``` On an i7-4790: ``` Benchmark 1: ./sapi/cli/php pack1.php Time (mean ± σ): 408.8 ms ± 3.4 ms [User: 406.1 ms, System: 1.6 ms] Range (min … max): 403.6 ms … 413.6 ms 10 runs Benchmark 2: ./sapi/cli/php_old pack1.php Time (mean ± σ): 451.7 ms ± 7.7 ms [User: 448.5 ms, System: 2.0 ms] Range (min … max): 442.8 ms … 461.2 ms 10 runs Summary ./sapi/cli/php pack1.php ran 1.11 ± 0.02 times faster than ./sapi/cli/php_old pack1.php Benchmark 1: ./sapi/cli/php pack2.php Time (mean ± σ): 239.3 ms ± 6.0 ms [User: 236.2 ms, System: 2.3 ms] Range (min … max): 233.2 ms … 256.8 ms 12 runs Benchmark 2: ./sapi/cli/php_old pack2.php Time (mean ± σ): 271.9 ms ± 3.3 ms [User: 269.7 ms, System: 1.3 ms] Range (min … max): 267.4 ms … 279.0 ms 11 runs Summary ./sapi/cli/php pack2.php ran 1.14 ± 0.03 times faster than ./sapi/cli/php_old pack2.php ``` On an i7-1185G7: ``` Benchmark 1: ./sapi/cli/php pack1.php Time (mean ± σ): 263.7 ms ± 1.8 ms [User: 262.6 ms, System: 0.9 ms] Range (min … max): 261.5 ms … 268.2 ms 11 runs Benchmark 2: ./sapi/cli/php_old pack1.php Time (mean ± σ): 303.3 ms ± 6.5 ms [User: 300.7 ms, System: 2.3 ms] Range (min … max): 297.4 ms … 318.1 ms 10 runs Summary ./sapi/cli/php pack1.php ran 1.15 ± 0.03 times faster than ./sapi/cli/php_old pack1.php Benchmark 1: ./sapi/cli/php pack2.php Time (mean ± σ): 156.7 ms ± 2.9 ms [User: 154.7 ms, System: 1.7 ms] Range (min … max): 151.6 ms … 164.7 ms 19 runs Benchmark 2: ./sapi/cli/php_old pack2.php Time (mean ± σ): 174.6 ms ± 3.3 ms [User: 171.9 ms, System: 2.3 ms] Range (min … max): 170.7 ms … 180.4 ms 17 runs Summary ./sapi/cli/php pack2.php ran 1.11 ± 0.03 times faster than ./sapi/cli/php_old pack2.php ``` Co-authored-by: divinity76@gmail.com --- ext/standard/basic_functions.c | 1 - ext/standard/pack.c | 208 ++++++--------------------------- ext/standard/pack.h | 22 ---- ext/standard/php_standard.h | 1 - 4 files changed, 36 insertions(+), 196 deletions(-) delete mode 100644 ext/standard/pack.h diff --git a/ext/standard/basic_functions.c b/ext/standard/basic_functions.c index f26bef3daa4e7..ea2f8884bdef6 100644 --- a/ext/standard/basic_functions.c +++ b/ext/standard/basic_functions.c @@ -299,7 +299,6 @@ PHP_MINIT_FUNCTION(basic) /* {{{ */ BASIC_MINIT_SUBMODULE(var) BASIC_MINIT_SUBMODULE(file) - BASIC_MINIT_SUBMODULE(pack) BASIC_MINIT_SUBMODULE(browscap) BASIC_MINIT_SUBMODULE(standard_filters) BASIC_MINIT_SUBMODULE(user_filters) diff --git a/ext/standard/pack.c b/ext/standard/pack.c index d4c5cc1f04cfa..eb6905952bedc 100644 --- a/ext/standard/pack.c +++ b/ext/standard/pack.c @@ -19,7 +19,6 @@ #include #include #include -#include "pack.h" #define INC_OUTPUTPOS(a,b) \ if ((a) < 0 || ((INT_MAX - outputpos)/((int)b)) < (a)) { \ @@ -30,10 +29,23 @@ } \ outputpos += (a)*(b); +typedef enum { + PHP_LITTLE_ENDIAN, + PHP_BIG_ENDIAN, +} php_pack_endianness; + #ifdef WORDS_BIGENDIAN -#define MACHINE_LITTLE_ENDIAN 0 +# define MACHINE_LITTLE_ENDIAN 0 +# define PHP_MACHINE_ENDIAN PHP_BIG_ENDIAN #else -#define MACHINE_LITTLE_ENDIAN 1 +# define MACHINE_LITTLE_ENDIAN 1 +# define PHP_MACHINE_ENDIAN PHP_LITTLE_ENDIAN +#endif + +#ifdef ZEND_ENABLE_ZVAL_LONG64 +# define PHP_LONG_BSWAP(u) ZEND_BYTES_SWAP64(u) +#else +# define PHP_LONG_BSWAP(u) ZEND_BYTES_SWAP32(u) #endif typedef ZEND_SET_ALIGNED(1, uint16_t unaligned_uint16_t); @@ -42,41 +54,17 @@ typedef ZEND_SET_ALIGNED(1, uint64_t unaligned_uint64_t); typedef ZEND_SET_ALIGNED(1, unsigned int unaligned_uint); typedef ZEND_SET_ALIGNED(1, int unaligned_int); -/* Mapping of byte from char (8bit) to long for machine endian */ -static int byte_map[1]; - -/* Mappings of bytes from int (machine dependent) to int for machine endian */ -static int int_map[sizeof(int)]; - -/* Mappings of bytes from shorts (16bit) for all endian environments */ -static int machine_endian_short_map[2]; -static int big_endian_short_map[2]; -static int little_endian_short_map[2]; - -/* Mappings of bytes from longs (32bit) for all endian environments */ -static int machine_endian_long_map[4]; -static int big_endian_long_map[4]; -static int little_endian_long_map[4]; - -#if SIZEOF_ZEND_LONG > 4 -/* Mappings of bytes from quads (64bit) for all endian environments */ -static int machine_endian_longlong_map[8]; -static int big_endian_longlong_map[8]; -static int little_endian_longlong_map[8]; -#endif - /* {{{ php_pack */ -static void php_pack(zval *val, size_t size, int *map, char *output) +static void php_pack(const zval *val, size_t size, php_pack_endianness endianness, char *output) { - size_t i; - char *v; + zend_ulong zl = zval_get_long(val); - convert_to_long(val); - v = (char *) &Z_LVAL_P(val); - - for (i = 0; i < size; i++) { - *output++ = v[map[i]]; + if ((endianness == PHP_LITTLE_ENDIAN) != MACHINE_LITTLE_ENDIAN) { + zl = PHP_LONG_BSWAP(zl); + zl >>= (sizeof(zl) - size) * 8; } + + memcpy(output, (const char *) &zl, size); } /* }}} */ @@ -509,7 +497,7 @@ PHP_FUNCTION(pack) case 'c': case 'C': while (arg-- > 0) { - php_pack(&argv[currentarg++], 1, byte_map, &ZSTR_VAL(output)[outputpos]); + php_pack(&argv[currentarg++], 1, PHP_MACHINE_ENDIAN, &ZSTR_VAL(output)[outputpos]); outputpos++; } break; @@ -518,16 +506,16 @@ PHP_FUNCTION(pack) case 'S': case 'n': case 'v': { - int *map = machine_endian_short_map; + php_pack_endianness endianness = PHP_MACHINE_ENDIAN; if (code == 'n') { - map = big_endian_short_map; + endianness = PHP_BIG_ENDIAN; } else if (code == 'v') { - map = little_endian_short_map; + endianness = PHP_LITTLE_ENDIAN; } while (arg-- > 0) { - php_pack(&argv[currentarg++], 2, map, &ZSTR_VAL(output)[outputpos]); + php_pack(&argv[currentarg++], 2, endianness, &ZSTR_VAL(output)[outputpos]); outputpos += 2; } break; @@ -536,7 +524,7 @@ PHP_FUNCTION(pack) case 'i': case 'I': while (arg-- > 0) { - php_pack(&argv[currentarg++], sizeof(int), int_map, &ZSTR_VAL(output)[outputpos]); + php_pack(&argv[currentarg++], sizeof(int), PHP_MACHINE_ENDIAN, &ZSTR_VAL(output)[outputpos]); outputpos += sizeof(int); } break; @@ -545,16 +533,16 @@ PHP_FUNCTION(pack) case 'L': case 'N': case 'V': { - int *map = machine_endian_long_map; + php_pack_endianness endianness = PHP_MACHINE_ENDIAN; if (code == 'N') { - map = big_endian_long_map; + endianness = PHP_BIG_ENDIAN; } else if (code == 'V') { - map = little_endian_long_map; + endianness = PHP_LITTLE_ENDIAN; } while (arg-- > 0) { - php_pack(&argv[currentarg++], 4, map, &ZSTR_VAL(output)[outputpos]); + php_pack(&argv[currentarg++], 4, endianness, &ZSTR_VAL(output)[outputpos]); outputpos += 4; } break; @@ -565,16 +553,16 @@ PHP_FUNCTION(pack) case 'Q': case 'J': case 'P': { - int *map = machine_endian_longlong_map; + php_pack_endianness endianness = PHP_MACHINE_ENDIAN; if (code == 'J') { - map = big_endian_longlong_map; + endianness = PHP_BIG_ENDIAN; } else if (code == 'P') { - map = little_endian_longlong_map; + endianness = PHP_LITTLE_ENDIAN; } while (arg-- > 0) { - php_pack(&argv[currentarg++], 8, map, &ZSTR_VAL(output)[outputpos]); + php_pack(&argv[currentarg++], 8, endianness, &ZSTR_VAL(output)[outputpos]); outputpos += 8; } break; @@ -1178,127 +1166,3 @@ PHP_FUNCTION(unpack) } } /* }}} */ - -/* {{{ PHP_MINIT_FUNCTION */ -PHP_MINIT_FUNCTION(pack) -{ - int i; - - if (MACHINE_LITTLE_ENDIAN) { - /* Where to get lo to hi bytes from */ - byte_map[0] = 0; - - for (i = 0; i < (int)sizeof(int); i++) { - int_map[i] = i; - } - - machine_endian_short_map[0] = 0; - machine_endian_short_map[1] = 1; - big_endian_short_map[0] = 1; - big_endian_short_map[1] = 0; - little_endian_short_map[0] = 0; - little_endian_short_map[1] = 1; - - machine_endian_long_map[0] = 0; - machine_endian_long_map[1] = 1; - machine_endian_long_map[2] = 2; - machine_endian_long_map[3] = 3; - big_endian_long_map[0] = 3; - big_endian_long_map[1] = 2; - big_endian_long_map[2] = 1; - big_endian_long_map[3] = 0; - little_endian_long_map[0] = 0; - little_endian_long_map[1] = 1; - little_endian_long_map[2] = 2; - little_endian_long_map[3] = 3; - -#if SIZEOF_ZEND_LONG > 4 - machine_endian_longlong_map[0] = 0; - machine_endian_longlong_map[1] = 1; - machine_endian_longlong_map[2] = 2; - machine_endian_longlong_map[3] = 3; - machine_endian_longlong_map[4] = 4; - machine_endian_longlong_map[5] = 5; - machine_endian_longlong_map[6] = 6; - machine_endian_longlong_map[7] = 7; - big_endian_longlong_map[0] = 7; - big_endian_longlong_map[1] = 6; - big_endian_longlong_map[2] = 5; - big_endian_longlong_map[3] = 4; - big_endian_longlong_map[4] = 3; - big_endian_longlong_map[5] = 2; - big_endian_longlong_map[6] = 1; - big_endian_longlong_map[7] = 0; - little_endian_longlong_map[0] = 0; - little_endian_longlong_map[1] = 1; - little_endian_longlong_map[2] = 2; - little_endian_longlong_map[3] = 3; - little_endian_longlong_map[4] = 4; - little_endian_longlong_map[5] = 5; - little_endian_longlong_map[6] = 6; - little_endian_longlong_map[7] = 7; -#endif - } - else { - zval val; - int size = sizeof(Z_LVAL(val)); - Z_LVAL(val)=0; /*silence a warning*/ - - /* Where to get hi to lo bytes from */ - byte_map[0] = size - 1; - - for (i = 0; i < (int)sizeof(int); i++) { - int_map[i] = size - (sizeof(int) - i); - } - - machine_endian_short_map[0] = size - 2; - machine_endian_short_map[1] = size - 1; - big_endian_short_map[0] = size - 2; - big_endian_short_map[1] = size - 1; - little_endian_short_map[0] = size - 1; - little_endian_short_map[1] = size - 2; - - machine_endian_long_map[0] = size - 4; - machine_endian_long_map[1] = size - 3; - machine_endian_long_map[2] = size - 2; - machine_endian_long_map[3] = size - 1; - big_endian_long_map[0] = size - 4; - big_endian_long_map[1] = size - 3; - big_endian_long_map[2] = size - 2; - big_endian_long_map[3] = size - 1; - little_endian_long_map[0] = size - 1; - little_endian_long_map[1] = size - 2; - little_endian_long_map[2] = size - 3; - little_endian_long_map[3] = size - 4; - -#if SIZEOF_ZEND_LONG > 4 - machine_endian_longlong_map[0] = size - 8; - machine_endian_longlong_map[1] = size - 7; - machine_endian_longlong_map[2] = size - 6; - machine_endian_longlong_map[3] = size - 5; - machine_endian_longlong_map[4] = size - 4; - machine_endian_longlong_map[5] = size - 3; - machine_endian_longlong_map[6] = size - 2; - machine_endian_longlong_map[7] = size - 1; - big_endian_longlong_map[0] = size - 8; - big_endian_longlong_map[1] = size - 7; - big_endian_longlong_map[2] = size - 6; - big_endian_longlong_map[3] = size - 5; - big_endian_longlong_map[4] = size - 4; - big_endian_longlong_map[5] = size - 3; - big_endian_longlong_map[6] = size - 2; - big_endian_longlong_map[7] = size - 1; - little_endian_longlong_map[0] = size - 1; - little_endian_longlong_map[1] = size - 2; - little_endian_longlong_map[2] = size - 3; - little_endian_longlong_map[3] = size - 4; - little_endian_longlong_map[4] = size - 5; - little_endian_longlong_map[5] = size - 6; - little_endian_longlong_map[6] = size - 7; - little_endian_longlong_map[7] = size - 8; -#endif - } - - return SUCCESS; -} -/* }}} */ diff --git a/ext/standard/pack.h b/ext/standard/pack.h deleted file mode 100644 index 5e12d55e0bc63..0000000000000 --- a/ext/standard/pack.h +++ /dev/null @@ -1,22 +0,0 @@ -/* - +----------------------------------------------------------------------+ - | Copyright (c) The PHP Group | - +----------------------------------------------------------------------+ - | This source file is subject to version 3.01 of the PHP license, | - | that is bundled with this package in the file LICENSE, and is | - | available through the world-wide-web at the following url: | - | https://www.php.net/license/3_01.txt | - | If you did not receive a copy of the PHP license and are unable to | - | obtain it through the world-wide-web, please send a note to | - | license@php.net so we can mail you a copy immediately. | - +----------------------------------------------------------------------+ - | Author: Rasmus Lerdorf | - +----------------------------------------------------------------------+ -*/ - -#ifndef PACK_H -#define PACK_H - -PHP_MINIT_FUNCTION(pack); - -#endif /* PACK_H */ diff --git a/ext/standard/php_standard.h b/ext/standard/php_standard.h index 78eba25f11a58..5bc792362bbd2 100644 --- a/ext/standard/php_standard.h +++ b/ext/standard/php_standard.h @@ -29,7 +29,6 @@ #include "php_ext_syslog.h" #include "php_filestat.h" #include "php_browscap.h" -#include "pack.h" #include "url.h" #include "pageinfo.h" #include "fsock.h" From 18a385faaa940a665387fe08c149604f8d399577 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Thu, 8 May 2025 23:14:07 +0200 Subject: [PATCH 2/3] Use ZEND_BYTES_SWAP32() for php_pack_reverse_int32() --- ext/standard/pack.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/ext/standard/pack.c b/ext/standard/pack.c index eb6905952bedc..0bdc9fc77ec05 100644 --- a/ext/standard/pack.c +++ b/ext/standard/pack.c @@ -76,10 +76,7 @@ ZEND_ATTRIBUTE_CONST static inline uint16_t php_pack_reverse_int16(uint16_t arg) /* {{{ php_pack_reverse_int32 */ ZEND_ATTRIBUTE_CONST static inline uint32_t php_pack_reverse_int32(uint32_t arg) { - uint32_t result; - result = ((arg & 0xFF) << 24) | ((arg & 0xFF00) << 8) | ((arg >> 8) & 0xFF00) | ((arg >> 24) & 0xFF); - - return result; + return ZEND_BYTES_SWAP32(arg); } /* }}} */ From 32e10937c6df16b3b8d325cd20d20fad72add608 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Wed, 18 Jun 2025 20:23:38 +0200 Subject: [PATCH 3/3] be fixes --- ext/standard/pack.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/ext/standard/pack.c b/ext/standard/pack.c index 0bdc9fc77ec05..5b094721346ad 100644 --- a/ext/standard/pack.c +++ b/ext/standard/pack.c @@ -61,7 +61,13 @@ static void php_pack(const zval *val, size_t size, php_pack_endianness endiannes if ((endianness == PHP_LITTLE_ENDIAN) != MACHINE_LITTLE_ENDIAN) { zl = PHP_LONG_BSWAP(zl); +#if MACHINE_LITTLE_ENDIAN zl >>= (sizeof(zl) - size) * 8; +#endif + } else { +#if !MACHINE_LITTLE_ENDIAN + zl <<= (sizeof(zl) - size) * 8; +#endif } memcpy(output, (const char *) &zl, size);