-
Notifications
You must be signed in to change notification settings - Fork 7.8k
ext/bcmath: Improving bcpow()
performance
#18099
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
cff64b6
to
511010a
Compare
There were still some things I wanted to change, so I reverted it to a draft. |
33bfabf
to
f303bd5
Compare
Ready for review. cc: @nielsdos |
…dard_vector_mul`.
…e`, and renamed to `bc_square_vector`.
…c_multiply_vector
a1f6bbf
to
76d0e61
Compare
I force pushed by mistake. I'll open it again once I've confirmed the code 🙏 |
76d0e61
to
6d00b7b
Compare
6d00b7b
to
757e3bc
Compare
I reverted to the original code. |
} | ||
|
||
size_t base_arr_size = BC_ARR_SIZE_FROM_LEN(base_len); | ||
size_t max_power_arr_size = base_arr_size * exponent; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can this overflow?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added check for base->n_len * exponent
.
So guaranteed not to overflow here. I added a comment.
b96f2d9
ext/bcmath/libbcmath/src/raise.c
Outdated
size_t max_power_arr_size = base_arr_size * exponent; | ||
|
||
/* The allocated memory area is reused on a rotational basis, so the same size is required. */ | ||
BC_VECTOR *buf = safe_emalloc(max_power_arr_size * 3, sizeof(BC_VECTOR), 0); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should probably be safe_emalloc(max_power_arr_size, 3 * sizeof(BC_VECTOR), 0);
for safety
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed in 2556951
ext/bcmath/libbcmath/src/raise.c
Outdated
/* Remove the leading zeros as they will be filled in later. */ | ||
while (*base_ptr++ == 0) { | ||
base_len--; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will already move base_ptr even if the first element is not 0. Did you mean this?
/* Remove the leading zeros as they will be filled in later. */ | |
while (*base_ptr++ == 0) { | |
base_len--; | |
} | |
/* Remove the leading zeros as they will be filled in later. */ | |
while (*base_ptr == 0) { | |
base_ptr++; | |
base_len--; | |
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, damn, you're absolutely right. Thanks!
Fixed in 2556951
bc_square_ex(power, &power, pwrscale); | ||
exponent = exponent >> 1; | ||
size_t base_len = base->n_len + base->n_scale; | ||
size_t power_len = base->n_len * exponent; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder which of these can overflow
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added check in b96f2d9
Thanks!
ext/bcmath/libbcmath/src/raise.c
Outdated
|
||
/* Pad with leading zeros if necessary. */ | ||
while (power_leading_zeros > sizeof(uint32_t)) { | ||
bc_write_bcd_representation(0, pptr); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is an inefficient way of writing the zeros, better use one call to memset instead of this while+for loops actually.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I had seen somewhere that memset was slow, so I had been avoiding it.
I've now updated the code to use memset.
in 2556951
ext/bcmath/bcmath.c
Outdated
if (!bc_raise(first, exponent, &result, scale)) { | ||
zend_throw_exception_ex(zend_ce_division_by_zero_error, 0, "Negative power of zero"); | ||
goto cleanup; | ||
switch (bc_raise(first, exponent, &result, scale)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The code to handle the errors is duplicated. I think it would be great to have a separate function that throws the correct exception when bc_raise fails. Then, here and in bcmath_number_pow_internal
you can call that function is bc_raise return value != BC_RAISE_STATUS_OK.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed in 20c9309
ext/bcmath/libbcmath/src/bcmath.h
Outdated
@@ -74,6 +74,10 @@ typedef struct bc_struct { | |||
#define MAX(a, b) ((a)>(b)?(a):(b)) | |||
#define MIN(a, b) ((a)>(b)?(b):(a)) | |||
|
|||
#ifndef SIZE_T_MAX |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's a standard macro SIZE_MAX in limits.h
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed in 54729fd
ext/bcmath/libbcmath/src/raise.c
Outdated
/* check overflow */ | ||
if (UNEXPECTED(base->n_len > SIZE_T_MAX / exponent)) { | ||
bc_free_num (result); | ||
*result = bc_copy_num(BCG(_one_)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is it necessary to return a number in result
anyway after this check fails? (Same question below)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Indeed, there was nothing that required action here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed in 91e3890
I ran 500,000 comparisons with PHP 8.3 results. (I would like to perform more comparisons, but PHP 8.3's
bcpow()
is too heavy and the comparisons never finish.)No problems were detected.
Benchmarks
The speed difference is especially noticeable when
base
is less than 1 and there are many leading zeros.This is because try to eliminate as many zeros as possible during the calculation, and then fill in the missing digits with zeros later.
1:
2:
3:
4: