Skip to content
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

Quick money (butchering this library for the glory of satan, and CPU cycles) #634

Merged
merged 48 commits into from
May 2, 2021

Conversation

Ocramius
Copy link
Contributor

@Ocramius Ocramius commented Apr 13, 2021

This is a draft: still playing around with it.

Ultimately, I'm mostly toying around with the library, removing bits that can be removed without substantial BC Breaks (yes, there will be BC breaks) and inlining code that can be inlined (yes, the code will be inlined).

I suggest avoiding an immediate code review until this is ready and I have stable performance results (most of the benchmarks were performed while my PC was under high load, so the values in the commit messages are not reliable).

In addition to all this, I still need to check if the GMP calculator can be used instead of BcMath, in order of priority, since GMP seems to generally be a bit quicker.

Notable improvements:

  • full type coverage (100% type coverage by vimeo/psalm)
  • 80%+ mutation test coverage (roave/infection-static-analysis-plugin)
  • removal of a lot of internal API
  • roughly 50% performance improvements in happy-path usage of Money, Number and Currency
  • removal of Calculator discovery by-default (you want a custom calculator? configure it manually!)

Benchmark results

❯ php -dopcache.enable_cli=1 ./vendor/bin/phpbench run --ref=ref_original_5 --retry-threshold=3 --iterations=15 --revs=1000 --warmup=2
PHPBench @git_tag@ running benchmarks...
with configuration file: /home/ocramius/Documents/moneyphp/money/phpbench.json
with PHP version 8.0.3, xdebug ❌, opcache ✔

\Benchmark\Money\NumberInstantiationBench

    benchConstructorWithZeroIntegerAmount...R1 I2 - [Mo0.210μs vs Mo0.163μs] +28.42% (±1.29%)
    benchConstructorWithPositiveIntegerAmou.R1 I8 - [Mo0.308μs vs Mo0.626μs] -50.87% (±0.82%)
    benchConstructorWithNegativeIntegerAmou.R2 I2 - [Mo0.305μs vs Mo0.683μs] -55.40% (±0.90%)
    benchConstructorWithZeroAndFractionalAm.R1 I7 - [Mo0.316μs vs Mo0.576μs] -45.04% (±0.78%)
    benchConstructorWithFractionalAmount....R1 I14 - [Mo0.436μs vs Mo1.144μs] -61.85% (±0.80%)
    benchConstructorWithNegativeFractionalA.R1 I7 - [Mo0.435μs vs Mo1.200μs] -63.75% (±1.15%)
    benchFromStringWithZeroIntegerAmount....R1 I3 - [Mo0.319μs vs Mo0.208μs] +53.54% (±0.99%)
    benchFromStringWithPositiveIntegerAmoun.R1 I4 - [Mo0.426μs vs Mo0.676μs] -37.02% (±1.05%)
    benchFromStringWithNegativeIntegerAmoun.R1 I14 - [Mo0.423μs vs Mo0.731μs] -42.22% (±0.91%)
    benchFromStringWithZeroAndFractionalAmo.R2 I8 - [Mo0.476μs vs Mo0.689μs] -30.97% (±1.00%)
    benchFromStringWithFractionalAmount.....R2 I14 - [Mo0.609μs vs Mo1.257μs] -51.53% (±0.79%)
    benchFromStringWithNegativeFractionalAm.R1 I13 - [Mo0.612μs vs Mo1.304μs] -53.11% (±1.06%)

\Benchmark\Money\MoneyOperationBench

    benchAdd................................R1 I14 - [Mo1.244μs vs Mo1.338μs] -7.07% (±1.45%)
    benchSubtract...........................R1 I14 - [Mo1.254μs vs Mo1.315μs] -4.65% (±1.36%)
    benchMultiply...........................R3 I10 - [Mo1.297μs vs Mo2.346μs] -44.70% (±0.96%)
    benchDivide.............................R3 I14 - [Mo2.105μs vs Mo3.074μs] -31.52% (±1.03%)
    benchSum................................R1 I2 - [Mo2.231μs vs Mo3.630μs] -38.56% (±0.89%)
    benchMin................................R1 I14 - [Mo1.195μs vs Mo1.317μs] -9.22% (±1.13%)
    benchMax................................R1 I2 - [Mo1.191μs vs Mo1.307μs] -8.83% (±0.59%)
    benchAvg................................R2 I12 - [Mo1.190μs vs Mo1.309μs] -9.12% (±1.10%)
    benchRatioOf............................R2 I14 - [Mo0.783μs vs Mo1.232μs] -36.48% (±0.69%)
    benchMod................................R1 I4 - [Mo0.761μs vs Mo0.781μs] -2.60% (±1.24%)
    benchIsSameCurrency.....................R1 I14 - [Mo0.081μs vs Mo0.107μs] -24.24% (±0.96%)
    benchIsZero.............................R1 I1 - [Mo0.242μs vs Mo0.226μs] +7.02% (±0.96%)
    benchAbsolute...........................R1 I14 - [Mo0.345μs vs Mo0.307μs] +12.60% (±0.99%)
    benchNegative...........................R1 I14 - [Mo1.396μs vs Mo1.595μs] -12.48% (±1.01%)
    benchIsPositive.........................R1 I8 - [Mo0.236μs vs Mo0.219μs] +7.94% (±0.80%)
    benchCompare............................R1 I14 - [Mo0.320μs vs Mo0.361μs] -11.35% (±0.84%)
    benchLessThan...........................R1 I0 - [Mo0.351μs vs Mo0.394μs] -10.89% (±0.46%)
    benchLessThanOrEqual....................R1 I14 - [Mo0.354μs vs Mo0.386μs] -8.06% (±0.73%)
    benchEquals.............................R1 I14 - [Mo0.391μs vs Mo0.141μs] +177.89% (±0.96%)
    benchGreaterThan........................R1 I2 - [Mo0.352μs vs Mo0.386μs] -8.93% (±0.68%)
    benchGreaterThanOrEqual.................R1 I8 - [Mo0.352μs vs Mo0.387μs] -9.02% (±0.63%)

\Benchmark\Money\MoneyInstantiationBench

    benchConstructorWithZeroIntegerAmount...R2 I14 - [Mo0.149μs vs Mo0.175μs] -15.11% (±0.73%)
    benchConstructorWithPositiveIntegerAmou.R1 I0 - [Mo0.174μs vs Mo0.245μs] -29.24% (±1.32%)
    benchConstructorWithNegativeIntegerAmou.R1 I14 - [Mo0.175μs vs Mo0.248μs] -29.54% (±0.77%)
    benchConstructorWithZeroStringAmount....R1 I4 - [Mo0.183μs vs Mo0.162μs] +13.28% (±0.59%)
    benchConstructorWithPositiveStringAmoun.R1 I3 - [Mo0.205μs vs Mo0.180μs] +13.46% (±0.54%)
    benchConstructorWithNegativeStringAmoun.R1 I8 - [Mo0.209μs vs Mo0.185μs] +12.83% (±0.74%)

Subjects: 39, Assertions: 0, Failures: 0, Errors: 0

CI check status:

  • Composer Normalize (pull_request) - Failing after 19s - somehow didn't check my updated composer.json? 🤔
  • PrettyCI — Code formatting - no longer relevant - can be disabled?
  • Build (5.6) Expected - no longer expected - need adjustment of branch protection rules pre-merge
  • Build (7.0) Expected - no longer expected - need adjustment of branch protection rules pre-merge
  • Build (7.1) Expected - no longer expected - need adjustment of branch protection rules pre-merge
  • Build (7.2) Expected - no longer expected - need adjustment of branch protection rules pre-merge
  • Build (7.3) Expected - no longer expected - need adjustment of branch protection rules pre-merge
  • Build (7.4) Expected - no longer expected - need adjustment of branch protection rules pre-merge
  • PHP-CS-Fixer Expected - no longer expected - need adjustment of branch protection rules pre-merge
  • PHPStan Expected - no longer expected - need adjustment of branch protection rules pre-merge

Fixes

In this change, we bumped relevant dependencies to stick to `php:^7.3` as minimum
supported `php` version (since `7.2` is EOL'd and no longer covered by security fixes).

To do that, we upgraded all dependencies, upgraded spec files to comply with newest phpspec.

This currently means that we have a few broken scenarios and failures:

```
----  failed examples

        Money/Parser/DecimalMoneyParser
  47  ✘ throws an exception when money includes currency symbol
        expected exception of class "Money\Exception\ParserExc...", but got
        [exc:PhpSpec\Exception\Example\ErrorException("16384: Passing a currency as string is deprecated since 3.1 and
        will be removed in 4.0. Please pass a Money\Currency instance instead. in
        /home/ocramius/Documents/moneyphp/money/src/Parser/DecimalMoneyParser.php line 50")].

        Money/Parser/DecimalMoneyParser
  52  ✘ throws an exception when money is not a valid decimal
        expected exception of class "Money\Exception\ParserExc...", but got
        [exc:PhpSpec\Exception\Example\ErrorException("16384: Passing a currency as string is deprecated since 3.1 and
        will be removed in 4.0. Please pass a Money\Currency instance instead. in
        /home/ocramius/Documents/moneyphp/money/src/Parser/DecimalMoneyParser.php line 50")].

----  broken examples

        Money/Exchange/ExchangerExchange
  27  ! is initializable
        could not reflect class Exchanger\Exchanger as it is marked final.

        Money/Exchange/ExchangerExchange
  32  ! is an exchange
        could not reflect class Exchanger\Exchanger as it is marked final.

        Money/Exchange/ExchangerExchange
  37  ! exchanges currencies
        could not reflect class Exchanger\Exchanger as it is marked final.

        Money/Exchange/ExchangerExchange
  53  ! throws an exception when cannot exchange currencies
        could not reflect class Exchanger\Exchanger as it is marked final.

        Money/Parser/DecimalMoneyParser
  31  ! parses money
        16384: Passing a currency as string is deprecated since 3.1 and will be removed in 4.0. Please pass a Money\Currency
        instance instead. in /home/ocramius/Documents/moneyphp/money/src/Parser/DecimalMoneyParser.php line 50

29 specs
185 examples (178 passed, 2 failed, 5 broken)
146ms
```
…sed, then they should be raised

No suppressing them, since otherwise they effectively go unnoticed. Having a suppressed `trigger_error()` means
that only side-effects get run, instead of proper exception conversion.
… number operations

These benchmarks cover the "most used" functionality of `Money`, and should highlight any
computational complexity overhead.
.github/workflows/ci.yml Show resolved Hide resolved
composer.json Show resolved Hide resolved
phpunit.xml.dist Show resolved Hide resolved
.php_cs.dist Outdated Show resolved Hide resolved
src/Calculator/BcMathCalculator.php Outdated Show resolved Hide resolved
src/Money.php Show resolved Hide resolved
src/Money.php Show resolved Hide resolved
src/Number.php Show resolved Hide resolved
src/Number.php Show resolved Hide resolved
src/Parser/DecimalMoneyParser.php Show resolved Hide resolved
@Ocramius Ocramius changed the title Quick money (butchering this library for the glory of satan) Quick money (butchering this library for the glory of satan, and CPU cycles) Apr 13, 2021
@frederikbosch
Copy link
Member

@sagikazarmark In my opinion these changes should target MoneyPHP 4.0 with PHP8.0+. This would mean that @Ocramius has the freedom to remove deprecation warnings and add types where possible. So then we have Money 3 for <= PHP7.4. I would then also create a separate branch for the development of 4.0. Would you agree?

@Ocramius
Copy link
Contributor Author

@frederikbosch your maintenance role, your rules: remember that bumping dependencies is not a BC break, but this branch DOES contain BC breaks, so it is a good idea to release a new major anyway.

Can I suggest to branch off 3.x for the old stuff meanwhile (to allow for bugfixes to land)?

@frederikbosch
Copy link
Member

@Ocramius That's why I asked the other maintainer @sagikazarmark to join the discussion, and see what he thinks. I would suggest to break dependencies. I know there are places in the library that still use floats for calculations; they should be removed in this cycle too. We could indeed branch off to a 3.x branch.

Moreover I want to discuss how we should store numbers internally. There is this Number object I created which is instantiated too many times I believe, right? (forgive me, it's been a long time I really worked on this lib). We might want to change this.

@Ocramius
Copy link
Contributor Author

Working on this again today - will consider the changes as if I were on v4, so there will be BC breaks.

There is this Number object I created which is instantiated too many times I believe, right? (forgive me, it's been a long time I really worked on this lib). We might want to change this.

Yes, overall something I'd like to change: use Number only when user input is involved, while operating with string internally at all times, when possible.

@frederikbosch
Copy link
Member

Wonderful. My suggestion is to go for PHP8.0 and above, but I hope @sagikazarmark has the opportunity to confirm my suggestion.

@Ocramius
Copy link
Contributor Author

My suggestion is to go for PHP8.0 and above

Works for me, and also makes my life easier with the whole type improvements. I don't think I'll wait for @sagikazarmark's input: that's mostly because I can (like everyone else) only dedicate limited time to this anyway, and I since v3 already supports PHP 7, there is no need to support it in v4.

@frederikbosch
Copy link
Member

Alright, go for it then. You are the one willing to spend time on this huge improve, whereas @sagikazarmark and I are the ones that do not have this time, so I guess we are happy with the changes you provide!

@sagikazarmark
Copy link
Collaborator

Sorry for being late to the party. Yeah, I think targeting 4.0 with these changes makes sense. I'm not sure about PHP 8.0, unless there is a specific reason (ie. a feature that we want to use, a dependency that only supports 8.0, etc).

I can create a 3.x branch, so we can manage required checks separately. I wonder if submitting these changes in smaller chunks would be possible (eg. PHP upgrades and build changes then every other change). It would make reviews easier and would make sure that subsequent PRs run the new build pipelines.

We should also discuss the fate of the current 4.0 milestone: https://github.com/moneyphp/money/milestone/4
Is there something from there that needs to be done or can we do those once 4.0 is tagged?

Thanks for putting the effort into this @Ocramius!

…n internal functions

Overall really hard to tell if the results are legit, but there are some improvements:

```
❯ php -dopcache.enable_cli=1 ./vendor/bin/phpbench run --ref=original --retry-threshold=5 --iterations=10
PHPBench @git_tag@ running benchmarks...
with configuration file: /home/ocramius/Documents/moneyphp/money/phpbench.json
with PHP version 8.0.3, xdebug ❌, opcache ✔

\Benchmark\Money\NumberInstantiationBench

    benchConstructorWithZeroIntegerAmount...R1 I0 - [Mo575.630μs vs Mo591.581μs] -2.70% (±2.20%)
    benchConstructorWithPositiveIntegerAmou.R7 I9 - [Mo551.930μs vs Mo550.683μs] +0.23% (±3.02%)
    benchConstructorWithNegativeIntegerAmou.R1 I1 - [Mo554.108μs vs Mo580.812μs] -4.60% (±1.80%)
    benchConstructorWithZeroAndFractionalAm.R1 I3 - [Mo543.759μs vs Mo595.014μs] -8.61% (±1.77%)
    benchConstructorWithFractionalAmount....R1 I1 - [Mo589.360μs vs Mo611.898μs] -3.68% (±1.92%)
    benchConstructorWithNegativeFractionalA.R1 I0 - [Mo573.149μs vs Mo580.123μs] -1.20% (±2.22%)

\Benchmark\Money\MoneyOperationBench

    benchAdd................................R1 I6 - [Mo999.123μs vs Mo987.689μs] +1.16% (±2.42%)
    benchSubtract...........................R1 I9 - [Mo985.683μs vs Mo936.192μs] +5.29% (±1.70%)
    benchMultiply...........................R1 I7 - [Mo1.003ms vs Mo1.057ms] -5.11% (±2.00%)
    benchDivide.............................R1 I9 - [Mo946.427μs vs Mo1.031ms] -8.19% (±3.11%)
    benchRatioOf............................R1 I2 - [Mo985.380μs vs Mo989.503μs] -0.42% (±2.17%)
    benchMod................................R2 I5 - [Mo391.671μs vs Mo401.023μs] -2.33% (±2.37%)
    benchIsSameCurrency.....................R1 I4 - [Mo2.000μs vs Mo2.000μs] 0.00% (±0.00%)
    benchIsZero.............................R10 I9 - [Mo384.644μs vs Mo426.642μs] -9.84% (±1.56%)
    benchAbsolute...........................R2 I9 - [Mo423.309μs vs Mo396.644μs] +6.72% (±2.35%)
    benchNegative...........................R6 I9 - [Mo981.397μs vs Mo983.110μs] -0.17% (±2.55%)
    benchIsPositive.........................R3 I9 - [Mo386.301μs vs Mo392.182μs] -1.50% (±2.37%)
    benchCompare............................R7 I8 - [Mo387.603μs vs Mo397.301μs] -2.44% (±3.22%)
    benchLessThan...........................R3 I9 - [Mo407.575μs vs Mo390.164μs] +4.46% (±2.31%)
    benchLessThanOrEqual....................R1 I9 - [Mo425.425μs vs Mo430.164μs] -1.10% (±1.34%)
    benchEquals.............................R1 I7 - [Mo2.000μs vs Mo3.000μs] -33.33% (±0.00%)
    benchGreaterThan........................R1 I7 - [Mo390.366μs vs Mo390.450μs] -0.02% (±2.10%)
    benchGreaterThanOrEqual.................R2 I9 - [Mo412.262μs vs Mo411.738μs] +0.13% (±1.99%)

\Benchmark\Money\MoneyInstantiationBench

    benchConstructorWithZeroIntegerAmount...R2 I4 - [Mo904.501μs vs Mo898.483μs] +0.67% (±2.20%)
    benchConstructorWithPositiveIntegerAmou.R1 I8 - [Mo915.419μs vs Mo937.671μs] -2.37% (±2.11%)
    benchConstructorWithNegativeIntegerAmou.R1 I5 - [Mo896.722μs vs Mo909.286μs] -1.38% (±1.30%)
    benchConstructorWithZeroStringAmount....R2 I4 - [Mo908.315μs vs Mo933.039μs] -2.65% (±2.32%)
    benchConstructorWithPositiveStringAmoun.R1 I8 - [Mo890.975μs vs Mo857.589μs] +3.89% (±1.99%)
    benchConstructorWithNegativeStringAmoun.R1 I2 - [Mo858.750μs vs Mo904.536μs] -5.06% (±2.54%)

Subjects: 29, Assertions: 0, Failures: 0, Errors: 0
```
…Calculator()` all the time

This is a minimal improvement on a very hot execution path
…ultiply()` and `#divide()`

Also here, no noticeable improvements other than a massive jump in `benchRatioOf`:

```
❯ php -dopcache.enable_cli=1 ./vendor/bin/phpbench run --ref=original --retry-threshold=5 --iterations=10
PHPBench @git_tag@ running benchmarks...
with configuration file: /home/ocramius/Documents/moneyphp/money/phpbench.json
with PHP version 8.0.3, xdebug ❌, opcache ✔

\Benchmark\Money\NumberInstantiationBench

    benchConstructorWithZeroIntegerAmount...R1 I0 - [Mo569.571μs vs Mo591.581μs] -3.72% (±1.94%)
    benchConstructorWithPositiveIntegerAmou.R2 I8 - [Mo599.841μs vs Mo550.683μs] +8.93% (±2.41%)
    benchConstructorWithNegativeIntegerAmou.R1 I4 - [Mo621.906μs vs Mo580.812μs] +7.08% (±2.40%)
    benchConstructorWithZeroAndFractionalAm.R1 I9 - [Mo570.344μs vs Mo595.014μs] -4.15% (±2.62%)
    benchConstructorWithFractionalAmount....R2 I3 - [Mo600.387μs vs Mo611.898μs] -1.88% (±2.04%)
    benchConstructorWithNegativeFractionalA.R1 I2 - [Mo590.276μs vs Mo580.123μs] +1.75% (±1.99%)

\Benchmark\Money\MoneyOperationBench

    benchAdd................................R1 I3 - [Mo999.916μs vs Mo987.689μs] +1.24% (±1.97%)
    benchSubtract...........................R1 I9 - [Mo924.575μs vs Mo936.192μs] -1.24% (±2.18%)
    benchMultiply...........................R3 I8 - [Mo992.857μs vs Mo1.057ms] -6.09% (±2.46%)
    benchDivide.............................R1 I1 - [Mo1.015ms vs Mo1.031ms] -1.54% (±2.58%)
    benchRatioOf............................R5 I8 - [Mo381.761μs vs Mo989.503μs] -61.42% (±1.89%)
    benchMod................................R1 I9 - [Mo396.292μs vs Mo401.023μs] -1.18% (±2.16%)
    benchIsSameCurrency.....................R2 I8 - [Mo4.000μs vs Mo2.000μs] +100.00% (±0.00%)
    benchIsZero.............................R1 I3 - [Mo393.902μs vs Mo426.642μs] -7.67% (±1.72%)
    benchAbsolute...........................R1 I4 - [Mo379.667μs vs Mo396.644μs] -4.28% (±2.79%)
    benchNegative...........................R1 I3 - [Mo979.540μs vs Mo983.110μs] -0.36% (±2.10%)
    benchIsPositive.........................R1 I4 - [Mo408.442μs vs Mo392.182μs] +4.15% (±2.21%)
    benchCompare............................R1 I8 - [Mo381.159μs vs Mo397.301μs] -4.06% (±1.87%)
    benchLessThan...........................R1 I6 - [Mo391.462μs vs Mo390.164μs] +0.33% (±1.71%)
    benchLessThanOrEqual....................R4 I6 - [Mo416.380μs vs Mo430.164μs] -3.20% (±2.67%)
    benchEquals.............................R3 I4 - [Mo4.000μs vs Mo3.000μs] +33.33% (±0.00%)
    benchGreaterThan........................R1 I5 - [Mo382.789μs vs Mo390.450μs] -1.96% (±1.71%)
    benchGreaterThanOrEqual.................R1 I1 - [Mo379.530μs vs Mo411.738μs] -7.82% (±1.33%)

\Benchmark\Money\MoneyInstantiationBench

    benchConstructorWithZeroIntegerAmount...R2 I9 - [Mo945.877μs vs Mo898.483μs] +5.27% (±2.50%)
    benchConstructorWithPositiveIntegerAmou.R1 I7 - [Mo887.787μs vs Mo937.671μs] -5.32% (±2.63%)
    benchConstructorWithNegativeIntegerAmou.R3 I7 - [Mo958.738μs vs Mo909.286μs] +5.44% (±2.60%)
    benchConstructorWithZeroStringAmount....R1 I0 - [Mo954.548μs vs Mo933.039μs] +2.31% (±2.40%)
    benchConstructorWithPositiveStringAmoun.R3 I9 - [Mo931.642μs vs Mo857.589μs] +8.64% (±2.42%)
    benchConstructorWithNegativeStringAmoun.R1 I2 - [Mo943.121μs vs Mo904.536μs] +4.27% (±1.88%)

Subjects: 29, Assertions: 0, Failures: 0, Errors: 0
```
Also added an `is_int()` special case to the constructor, since we often deal with
integer values when constructing a `Money` instance, so we don't need to check for
a more expensive `filter_var()` operation.
…nt equality is also non-matching

This moves away a function call that is otherwise repeated very often, and which can lead to a lot
of runtime overhead for no reason.

Instead, we do this comparison only when the amount is the same (direct equality, faster for the engine).
While calling `Currency#equals(Currency)` may indeed look better from a domain perspective,
`Currency` is `final`, and currency comparison is one of the most frequent operations performed
on `Money` (for internal assertions).

Therefore, removing the method call is both feasible and beneficial for performance:

```
❯ php -dopcache.enable_cli=1 ./vendor/bin/phpbench run --ref=original --retry-threshold=5 --iterations=10
PHPBench @git_tag@ running benchmarks...
with configuration file: /home/ocramius/Documents/moneyphp/money/phpbench.json
with PHP version 8.0.3, xdebug ❌, opcache ✔

\Benchmark\Money\NumberInstantiationBench

    benchConstructorWithZeroIntegerAmount...R3 I7 - [Mo561.632μs vs Mo591.581μs] -5.06% (±3.01%)
    benchConstructorWithPositiveIntegerAmou.R1 I7 - [Mo542.669μs vs Mo550.683μs] -1.46% (±1.00%)
    benchConstructorWithNegativeIntegerAmou.R1 I2 - [Mo579.585μs vs Mo580.812μs] -0.21% (±1.60%)
    benchConstructorWithZeroAndFractionalAm.R3 I8 - [Mo574.055μs vs Mo595.014μs] -3.52% (±1.86%)
    benchConstructorWithFractionalAmount....R1 I9 - [Mo580.926μs vs Mo611.898μs] -5.06% (±2.09%)
    benchConstructorWithNegativeFractionalA.R2 I8 - [Mo587.192μs vs Mo580.123μs] +1.22% (±2.55%)

\Benchmark\Money\MoneyOperationBench

    benchAdd................................R1 I8 - [Mo985.200μs vs Mo987.689μs] -0.25% (±2.61%)
    benchSubtract...........................R1 I8 - [Mo985.114μs vs Mo936.192μs] +5.23% (±2.70%)
    benchMultiply...........................R1 I0 - [Mo988.329μs vs Mo1.057ms] -6.52% (±2.69%)
    benchDivide.............................R1 I7 - [Mo933.611μs vs Mo1.031ms] -9.43% (±0.93%)
    benchRatioOf............................R4 I4 - [Mo392.845μs vs Mo989.503μs] -60.30% (±1.70%)
    benchMod................................R1 I3 - [Mo421.904μs vs Mo401.023μs] +5.21% (±1.72%)
    benchIsSameCurrency.....................R2 I6 - [Mo1.000μs vs Mo2.000μs] -50.00% (±0.00%)
    benchIsZero.............................R1 I7 - [Mo400.636μs vs Mo426.642μs] -6.10% (±1.00%)
    benchAbsolute...........................R10 I9 - [Mo397.953μs vs Mo396.644μs] +0.33% (±2.76%)
    benchNegative...........................R1 I6 - [Mo955.247μs vs Mo983.110μs] -2.83% (±2.45%)
    benchIsPositive.........................R1 I0 - [Mo405.106μs vs Mo392.182μs] +3.30% (±2.71%)
    benchCompare............................R3 I9 - [Mo401.284μs vs Mo397.301μs] +1.00% (±2.12%)
    benchLessThan...........................R1 I9 - [Mo390.481μs vs Mo390.164μs] +0.08% (±1.84%)
    benchLessThanOrEqual....................R1 I5 - [Mo395.329μs vs Mo430.164μs] -8.10% (±2.53%)
    benchEquals.............................R10 I9 - [Mo1.000μs vs Mo3.000μs] -66.67% (±0.00%)
    benchGreaterThan........................R1 I0 - [Mo414.980μs vs Mo390.450μs] +6.28% (±2.71%)
    benchGreaterThanOrEqual.................R1 I1 - [Mo385.041μs vs Mo411.738μs] -6.48% (±2.64%)

\Benchmark\Money\MoneyInstantiationBench

    benchConstructorWithZeroIntegerAmount...R1 I9 - [Mo954.935μs vs Mo898.483μs] +6.28% (±1.18%)
    benchConstructorWithPositiveIntegerAmou.R1 I3 - [Mo913.027μs vs Mo937.671μs] -2.63% (±2.09%)
    benchConstructorWithNegativeIntegerAmou.R1 I0 - [Mo926.714μs vs Mo909.286μs] +1.92% (±2.25%)
    benchConstructorWithZeroStringAmount....R2 I9 - [Mo998.900μs vs Mo933.039μs] +7.06% (±2.79%)
    benchConstructorWithPositiveStringAmoun.R2 I4 - [Mo927.877μs vs Mo857.589μs] +8.20% (±2.17%)
    benchConstructorWithNegativeStringAmoun.R2 I9 - [Mo951.571μs vs Mo904.536μs] +5.20% (±2.64%)

Subjects: 29, Assertions: 0, Failures: 0, Errors: 0
```
…unt()` operation that can be optimized by opcache

```
❯ php -dopcache.enable_cli=1 ./vendor/bin/phpbench run --ref=original --retry-threshold=5 --iterations=100 --filter=Avg
PHPBench @git_tag@ running benchmarks...
with configuration file: /home/ocramius/Documents/moneyphp/money/phpbench.json
with PHP version 8.0.3, xdebug ❌, opcache ✔

\Benchmark\Money\MoneyOperationBench

    benchAvg................................R1 I63 - [Mo399.867μs vs Mo404.221μs] -1.08% (±2.29%)

Subjects: 1, Assertions: 0, Failures: 0, Errors: 0
```
…eading from `bcadd()` and `bcsub()` operations
…er#__construct()` code, adjusted ctor parameter types
…t)` comparison to see if two strings are equivalent

This simplifies checking for the validity of `Number` input values
src/Number.php Show resolved Hide resolved
src/Exchange/ExchangerExchange.php Show resolved Hide resolved
@Ocramius
Copy link
Contributor Author

@frederikbosch should be OK for another round

@frederikbosch
Copy link
Member

How about merging in? It's so huge that I have to work with it in order to find improvements.

@Ocramius
Copy link
Contributor Author

How about merging in? It's so huge that I have to work with it in order to find improvements.

Given the previous review, I suggest reviewing the individual (new) commits one-by-one, since I try to keep commit messages as relevant as possible.

@Ocramius
Copy link
Contributor Author

@frederikbosch just realized that yours may be a typo in the message: from my PoV, this is mergeable, and I have decent confidence in the amount of checks I've added here to avoid major regressions.

Minor regressions are unavoidable, but that's normality in OSS: https://xkcd.com/1172/

@Ocramius
Copy link
Contributor Author

I'm releasing this on a fork to internally test it on a customer project: IMO this is mergeable here too 👍

Ocramius added a commit to Ocramius/money that referenced this pull request Apr 26, 2021
@frederikbosch frederikbosch merged commit 4f9c4fa into moneyphp:master May 2, 2021
@frederikbosch
Copy link
Member

Merged, @Ocramius thanks a lot for all your work here. It is much appreciated by us maintainers, but I guess also by many users. I will try my best to come up with a release - first alpha/beta - as soon as possible.

@frederikbosch
Copy link
Member

@Ocramius You added roave/infection-static-analysis-plugin to this package in this PR. Yesterday the MSI reached a point below the 78% that you have set. I have no idea how to approach this, other than lowering this number. Is there any advice you can give in this regard?

@Ocramius
Copy link
Contributor Author

Yesterday the MSI reached a point below the 78% that you have set.

How did it reach it? What was the change? Was there a dependency change?

@frederikbosch
Copy link
Member

I added ext-filter and ext-json after running maglnet/composer-require-checker.

@Ocramius
Copy link
Contributor Author

@frederikbosch do you have a diff somewhere? Perhaps composer update changed something else?

@frederikbosch
Copy link
Member

Yes.

@Ocramius
Copy link
Contributor Author

@frederikbosch that bosh does a general composer update, which also happened to update infection/infection from 0.20.2 to 0.23.0.

Newer versions of infection/infection apply MORE mutators, and therefore need more effort in testing (especially around regular expressions, for which I think new mutators were introduced).

My suggestion is to:

If you need any help with it, I can probably hop on a call this afternoon CEST.

@frederikbosch
Copy link
Member

composer update nothing? Never heard of before, very nice! See #663. Thanks, for the comprehensive remarks. I will try to add dependabot later today.

@bcremer
Copy link
Contributor

bcremer commented Apr 6, 2023

I'm optimizing an application that deals with lots of MoneyPHP instances and operations (50k+ instances). The MoneyPHP instantiation and operations sum up to a few hundred milliseconds for a web request so we needed to optimize that.

With the benchmarks provided by @Ocramius I discovered that the add and subtract orations are considerable faster with the GmpCalculator. Most other operations are faster with the default BcMathCalculator.

To get the most performance in our application I wrote a delegating calculator that uses both implementations.

/**
 * Optimized `Money\Calculator` that delegates to either 
 * `BcMathCalculator` or `GmpCalculator` depending on the operation.
 *
 * `GmpCalculator` is approx 25% faster for add and subtract.
 * `BcMathCalculator` is faster for all decimal operations.
 */
class DelegatingCalculator implements Calculator
{
    public static function compare(string $a, string $b): int
    {
        return BcMathCalculator::compare($a, $b);
    }

    public static function add(string $amount, string $addend): string
    {
        return GmpCalculator::add($amount, $addend);
    }
    
[...]

Money::registerCalculator(DelegatingCalculator::class);

Sorry for responding in this old PR but maybe some one else finds this useful.

@frederikbosch
Copy link
Member

Do you have some benchmarks? Maybe create a PR with a PerformanceOptimizedDelegateCalculator class? Tests are easy since you can simply extend the abstract CalculatorTestCase.

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