mirror of
https://github.com/php/php-src.git
synced 2026-03-24 00:02:20 +01:00
Optimize pack()
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
```
Closes GH-18524.
Co-authored-by: divinity76 <divinity76@gmail.com>
This commit is contained in:
3
NEWS
3
NEWS
@@ -2,6 +2,9 @@ PHP NEWS
|
||||
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||
?? ??? ????, PHP 8.5.0alpha2
|
||||
|
||||
- Standard:
|
||||
. Optimized pack(). (nielsdos, divinity76)
|
||||
|
||||
- URI:
|
||||
. Return the singleton UrlValidationErrorType instances from Uri\WhatWg\Url
|
||||
instead of creating new objects that are different from the singleton.
|
||||
|
||||
@@ -619,6 +619,7 @@ PHP 8.5 UPGRADE NOTES
|
||||
. Improved performance of urlencode() and rawurlencode().
|
||||
. Improved unpack() performance with nameless repetitions by avoiding
|
||||
creating temporary strings and reparsing them.
|
||||
. Improved pack() performance.
|
||||
|
||||
- XMLReader:
|
||||
. Improved property access performance.
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
#include <stdlib.h>
|
||||
#include <errno.h>
|
||||
#include <sys/types.h>
|
||||
#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,23 @@ 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);
|
||||
#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);
|
||||
}
|
||||
/* }}} */
|
||||
|
||||
@@ -88,10 +82,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);
|
||||
}
|
||||
/* }}} */
|
||||
|
||||
@@ -509,7 +500,7 @@ too_few_args:
|
||||
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 +509,16 @@ too_few_args:
|
||||
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 +527,7 @@ too_few_args:
|
||||
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 +536,16 @@ too_few_args:
|
||||
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 +556,16 @@ too_few_args:
|
||||
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 +1169,3 @@ no_output:
|
||||
}
|
||||
}
|
||||
/* }}} */
|
||||
|
||||
/* {{{ 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;
|
||||
}
|
||||
/* }}} */
|
||||
|
||||
@@ -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 <rasmus@lerdorf.on.ca> |
|
||||
+----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
#ifndef PACK_H
|
||||
#define PACK_H
|
||||
|
||||
PHP_MINIT_FUNCTION(pack);
|
||||
|
||||
#endif /* PACK_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"
|
||||
|
||||
Reference in New Issue
Block a user