Micro-optimize double comparison (#11061)

When using ZEND_NORMALIZE_BOOL(a - b) where a and b are doubles, this
generates the following instruction sequence on x64:
subsd   xmm0, xmm1
pxor    xmm1, xmm1
comisd  xmm0, xmm1
...

whereas if we use ZEND_THREEWAY_COMPARE we get two instructions less:
ucomisd xmm0, xmm1

The only difference is that the threeway compare uses *u*comisd instead
of comisd. The difference is that it will cause a FP signal if a
signaling NAN is used, but as far as I'm aware this doesn't matter for
our use case.

Similarly, the amount of instructions on AArch64 is also quite a bit
lower for this code compared to the old code.

** Results **

Using the benchmark https://gist.github.com/nielsdos/b36517d81a1af74d96baa3576c2b70df
I used hyperfine: hyperfine --runs 25 --warmup 3 './sapi/cli/php sort_double.php'
No extensions such as opcache used during benchmarking.

BEFORE THIS PATCH
-----------------
  Time (mean ± σ):     255.5 ms ±   2.2 ms    [User: 251.0 ms, System: 2.5 ms]
  Range (min … max):   251.5 ms … 260.7 ms    25 runs

AFTER THIS PATCH
----------------
  Time (mean ± σ):     236.2 ms ±   2.8 ms    [User: 228.9 ms, System: 5.0 ms]
  Range (min … max):   231.5 ms … 242.7 ms    25 runs
This commit is contained in:
Niels Dossche 2023-04-14 18:22:42 +02:00 committed by GitHub
parent 8d5e06dc94
commit a0476fd32f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 9 additions and 18 deletions

View File

@ -2109,7 +2109,7 @@ ZEND_API int ZEND_FASTCALL numeric_compare_function(zval *op1, zval *op2) /* {{{
d1 = zval_get_double(op1);
d2 = zval_get_double(op2);
return ZEND_NORMALIZE_BOOL(d1 - d2);
return ZEND_THREEWAY_COMPARE(d1, d2);
}
/* }}} */
@ -2131,8 +2131,7 @@ static int compare_long_to_string(zend_long lval, zend_string *str) /* {{{ */
}
if (type == IS_DOUBLE) {
double diff = (double) lval - str_dval;
return ZEND_NORMALIZE_BOOL(diff);
return ZEND_THREEWAY_COMPARE((double) lval, str_dval);
}
zend_string *lval_as_str = zend_long_to_str(lval);
@ -2150,15 +2149,11 @@ static int compare_double_to_string(double dval, zend_string *str) /* {{{ */
uint8_t type = is_numeric_string(ZSTR_VAL(str), ZSTR_LEN(str), &str_lval, &str_dval, 0);
if (type == IS_LONG) {
double diff = dval - (double) str_lval;
return ZEND_NORMALIZE_BOOL(diff);
return ZEND_THREEWAY_COMPARE(dval, (double) str_lval);
}
if (type == IS_DOUBLE) {
if (dval == str_dval) {
return 0;
}
return ZEND_NORMALIZE_BOOL(dval - str_dval);
return ZEND_THREEWAY_COMPARE(dval, str_dval);
}
zend_string *dval_as_str = zend_double_to_str(dval);
@ -2180,17 +2175,13 @@ ZEND_API int ZEND_FASTCALL zend_compare(zval *op1, zval *op2) /* {{{ */
return Z_LVAL_P(op1)>Z_LVAL_P(op2)?1:(Z_LVAL_P(op1)<Z_LVAL_P(op2)?-1:0);
case TYPE_PAIR(IS_DOUBLE, IS_LONG):
return ZEND_NORMALIZE_BOOL(Z_DVAL_P(op1) - (double)Z_LVAL_P(op2));
return ZEND_THREEWAY_COMPARE(Z_DVAL_P(op1), (double)Z_LVAL_P(op2));
case TYPE_PAIR(IS_LONG, IS_DOUBLE):
return ZEND_NORMALIZE_BOOL((double)Z_LVAL_P(op1) - Z_DVAL_P(op2));
return ZEND_THREEWAY_COMPARE((double)Z_LVAL_P(op1), Z_DVAL_P(op2));
case TYPE_PAIR(IS_DOUBLE, IS_DOUBLE):
if (Z_DVAL_P(op1) == Z_DVAL_P(op2)) {
return 0;
} else {
return ZEND_NORMALIZE_BOOL(Z_DVAL_P(op1) - Z_DVAL_P(op2));
}
return ZEND_THREEWAY_COMPARE(Z_DVAL_P(op1), Z_DVAL_P(op2));
case TYPE_PAIR(IS_ARRAY, IS_ARRAY):
return zend_compare_arrays(op1, op2);

View File

@ -247,7 +247,7 @@ static int spl_ptr_pqueue_elem_cmp_long(void *x, void *y, zval *object) {
static int spl_ptr_pqueue_elem_cmp_double(void *x, void *y, zval *object) {
double a = Z_DVAL(((spl_pqueue_elem*) x)->priority);
double b = Z_DVAL(((spl_pqueue_elem*) y)->priority);
return ZEND_NORMALIZE_BOOL(a - b);
return ZEND_THREEWAY_COMPARE(a, b);
}
static spl_ptr_heap *spl_ptr_heap_init(spl_ptr_heap_cmp_func cmp, spl_ptr_heap_ctor_func ctor, spl_ptr_heap_dtor_func dtor, size_t elem_size) /* {{{ */

View File

@ -164,7 +164,7 @@ static zend_always_inline int php_array_key_compare_numeric_unstable_i(Bucket *f
} else {
d2 = (double)(zend_long)s->h;
}
return ZEND_NORMALIZE_BOOL(d1 - d2);
return ZEND_THREEWAY_COMPARE(d1, d2);
}
}
/* }}} */