Skip to content

ext/bcmath: bcpow() performance improvement #15790

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

Merged
merged 8 commits into from
Sep 17, 2024

Conversation

jorgsowa
Copy link
Contributor

@jorgsowa jorgsowa commented Sep 7, 2024

This PR improves the performance of the BCMath function bcpow.

We don't need to perform the usual multiplication when we raise a number to a specific set of numbers. I modified the content of bc_mulitply and removed all the redundant operations for the same number.

For small exponents, there is neglectable improvement, but for bigger there is up to 5%. However, I'm not quite happy with this benchmark and I need to use something else.

Benchmark:

<?php

$numbers = ['10', '2.1234567', '2'];
$exponents = [1, 2, 10, 64, 100];

foreach($numbers as $number) {
    foreach($exponents as $exponent) {
        echo "Number: $number, Exponent: $exponent = ";
        $start = microtime(true);
        for ($i = 0; $i < 1000000; $i++) {
            bcpow($number, $exponent, 20);
        }
        echo microtime(true) - $start . PHP_EOL;
    }
}
Number Exponent Before After Difference
10 1 0.369961977005 0.3674750328064 -0.67 %
10 2 0.39027285575867 0.38206386566162 -2.14 %
10 10 0.73798298835754 0.70764207839966 -4.28 %
10 64 1.6131219863892 1.4904029369354 -8.23 %
10 100 2.5796089172363 2.4571402072906 -4.98 %
2.1234567 1 0.36139988899231 0.36435699462891 +0.82 %
2.1234567 2 0.448086977005 0.44263195991516 -1.23 %
2.1234567 10 1.6505491733551 1.5602447986603 -5.78 %
2.1234567 64 6.6499609947205 6.3584640026093 -4.58 %
2.1234567 100 14.168872833252 13.804186105728 -2.64 %
2 1 0.33516597747803 0.32968902587891 -1.66 %
2 2 0.38029599189758 0.36791205406189 -3.36 %
2 10 0.59442806243896 0.5603358745575 -6.08 %
2 64 0.94932103157043 0.86743497848511 -9.44 %
2 100 1.4967222213745 1.39244389534 -7.48 %

Edit:
Benchmark using hyperfine:
0. Benchmark script

foreach($numbers as $number) {
    foreach($exponents as $exponent) {
        echo "Number: $number, Exponent: $exponent = ";
        $start = microtime(true);
        for ($i = 0; $i < 10000; $i++) {
            bcpow($number, $exponent, 20);
        }
        echo microtime(true) - $start . PHP_EOL;
    }
}
  1. Float numbers, small exp
$numbers = ['1.0432151', '2.1234567', '0.798432'];
$exponents = [2, 5, 7, 8, 10];

Benchmark 1: sapi/cli/php test_float_numbers_small_exp.php
Time (mean ± σ): 168.0 ms ± 1.1 ms [User: 164.3 ms, System: 1.8 ms]
Range (min … max): 166.8 ms … 171.4 ms 17 runs

Benchmark 2: sapi/cli/php_bc/bin/php test_float_numbers_small_exp.php
Time (mean ± σ): 160.1 ms ± 1.1 ms [User: 156.1 ms, System: 1.8 ms]
Range (min … max): 158.3 ms … 161.9 ms 18 runs

Summary
sapi/cli/php_bc/bin/php test_float_numbers_small_exp.php ran
1.05 ± 0.01 times faster than sapi/cli/php test_float_numbers_small_exp.php

  1. Float numbers, big exp
$numbers = ['1.0432151', '2.1234567', '0.798432'];
$exponents = [32, 64, 67, 100];

Benchmark 1: sapi/cli/php test_float_numbers_big_exp.php
Time (mean ± σ): 943.9 ms ± 5.2 ms [User: 934.5 ms, System: 2.2 ms]
Range (min … max): 936.8 ms … 952.4 ms 10 runs

Benchmark 2: sapi/cli/php_bc/bin/php test_float_numbers_big_exp.php
Time (mean ± σ): 872.4 ms ± 1.7 ms [User: 865.4 ms, System: 2.1 ms]
Range (min … max): 870.8 ms … 876.0 ms 10 runs

Summary
sapi/cli/php_bc/bin/php test_float_numbers_big_exp.php ran
1.08 ± 0.01 times faster than sapi/cli/php test_float_numbers_big_exp.php

  1. Int numbers, small exp
$numbers = ['10', '72', '11', '99'];
$exponents = [2, 5, 7, 8, 10];

Benchmark 1: sapi/cli/php test_int_numbers_small_exp.php
Time (mean ± σ): 118.1 ms ± 0.5 ms [User: 114.9 ms, System: 1.7 ms]
Range (min … max): 117.4 ms … 119.5 ms 24 runs

Benchmark 2: sapi/cli/php_bc/bin/php test_int_numbers_small_exp.php
Time (mean ± σ): 112.9 ms ± 0.4 ms [User: 109.9 ms, System: 1.6 ms]
Range (min … max): 112.5 ms … 114.1 ms 26 runs

Summary
sapi/cli/php_bc/bin/php test_int_numbers_small_exp.php ran
1.05 ± 0.01 times faster than sapi/cli/php test_int_numbers_small_exp.php

  1. Int numbers, big exp
$numbers = ['10', '72', '11', '99'];
$exponents = [32, 64, 67, 100];

Benchmark 1: sapi/cli/php test_int_numbers_big_exp.php
Time (mean ± σ): 355.1 ms ± 0.8 ms [User: 350.9 ms, System: 1.8 ms]
Range (min … max): 353.7 ms … 356.2 ms 10 runs

Benchmark 2: sapi/cli/php_bc/bin/php test_int_numbers_big_exp.php
Time (mean ± σ): 331.5 ms ± 0.4 ms [User: 327.1 ms, System: 1.8 ms]
Range (min … max): 330.9 ms … 332.1 ms 10 runs

Summary
sapi/cli/php_bc/bin/php test_int_numbers_big_exp.php ran
1.07 ± 0.00 times faster than sapi/cli/php test_int_numbers_big_exp.php

  1. Large numbers
$numbers = ['5614987514.43124124321', '21318945190.432132591','954319523410491'];
$exponents = [2, 5, 7, 8, 10];

Benchmark 1: sapi/cli/php test_large_numbers.php
Time (mean ± σ): 303.0 ms ± 1.3 ms [User: 298.8 ms, System: 1.8 ms]
Range (min … max): 300.9 ms … 305.6 ms 10 runs

Benchmark 2: sapi/cli/php_bc/bin/php test_large_numbers.php
Time (mean ± σ): 285.7 ms ± 2.5 ms [User: 281.0 ms, System: 1.9 ms]
Range (min … max): 283.3 ms … 292.0 ms 10 runs

Summary
sapi/cli/php_bc/bin/php test_large_numbers.php ran
1.06 ± 0.01 times faster than sapi/cli/php test_large_numbers.php

Copy link
Member

@nielsdos nielsdos left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this! It generally looks good and I only have remarks about some details of the code.
Regarding the benchmark: I prefer to use hyperfine. This runs the code multiple times and computes the mean and standard deviation and filters outliers. (Note that you will still need a for loop in the PHP script because otherwise runtime gets dominated by startup and shutdown).
Please also bench with some longer numbers.

@jorgsowa
Copy link
Contributor Author

jorgsowa commented Sep 8, 2024

Thank you for the review. I changed everything mentioned and tested using hyperfine. The difference depending on the number and exponent shape is between 1.05 and 1.08 faster than the previous solution. I attached the results into the description.

@jorgsowa jorgsowa requested a review from nielsdos September 8, 2024 07:46
Copy link
Member

@nielsdos nielsdos left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM cc @SakiTakamachi Please also check this
Also: Do we need RM approval for optimizations like this?

Copy link
Member

@SakiTakamachi SakiTakamachi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, thx!

@nielsdos

This shouldn't be required as it doesn't affect userland, but it's better to have RM approval.

I can approve this and merge it into 8.4.

@nielsdos
Copy link
Member

Alright thanks! I'll let you merge it then 🙂

@nielsdos
Copy link
Member

Seems like this was forgotten, I'll merge it now.

@nielsdos nielsdos merged commit 306dedc into php:master Sep 17, 2024
10 checks passed
@SakiTakamachi
Copy link
Member

ahhh, sorry, thanks

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants