Yaffle / BigDecimal

a polyfill for decimal propocal
MIT License
51 stars 7 forks source link

toString/toFixed are very slow compared to Decimal.js #10

Closed imirkin closed 1 year ago

imirkin commented 1 year ago

I put together a quick benchmark as I was noticing that toString/toFixed felt slow.

Click to expand benchmark ```js import Benchmark from 'benchmark'; import {BigDecimal} from './BigDecimal.js'; import {default as BigDecimal2} from './BigDecimalByDecimal.js.js'; var suite = new Benchmark.Suite; let big = BigDecimal.BigDecimal(987234798234987324987n); let decimal = BigDecimal2.BigDecimal(987234798234987324987n); // add tests suite.add('BigDecimal#toFixed', function() { big.toFixed(0); }) .add('Decimal#toFixed', function() { decimal.toFixed(0); }) // add listeners .on('cycle', function(event) { console.log(String(event.target)); }) .on('complete', function() { console.log('Fastest is ' + this.filter('fastest').map('name')); }) // run async .run({ 'async': false }); var suite = new Benchmark.Suite; // add tests suite.add('BigDecimal#toString', function() { big.toString(); }) .add('Decimal#toString', function() { decimal.toString(); }) // add listeners .on('cycle', function(event) { console.log(String(event.target)); }) .on('complete', function() { console.log('Fastest is ' + this.filter('fastest').map('name')); }) // run async .run({ 'async': false }); ```

The results aren't great:

BigDecimal#toFixed x 1,113,106 ops/sec ±0.69% (91 runs sampled)
Decimal#toFixed x 2,903,122 ops/sec ±1.60% (87 runs sampled)
Fastest is Decimal#toFixed
BigDecimal#toString x 546,881 ops/sec ±1.73% (83 runs sampled)
Decimal#toString x 11,381,407 ops/sec ±3.81% (81 runs sampled)
Fastest is Decimal#toString

I'm running this on an ancient CPU (Intel Core i7-920), but I doubt that the lack of AVX/AVX2 is what's doing me in here. Feel free to try out the above benchmark code after doing npm i benchmark, perhaps CPU type really does play into it. In case it matters, I'm using node v20.2.0, although the ultimate target is a web browser.

I'll be digging into the implementation of both this and decimal.js/big.js to see what can be copied/changed, or if there are any obvious shortcomings, but also very much open to suggestions.

I did notice that 10n ** n is faster when the power is large (e.g. 100), but slower for smaller powers (e.g. 10) than BigInt("1" + "0".repeat(n)). But I doubt that's the whole story.

imirkin commented 1 year ago

As of commit b356299808b8aaad38c518db64906dfa506b3aa2, the situation is much better. With the original value (which was my mashing the keyboard randomly):

987234798234987324987n:

BigDecimal#toFixed x 2,805,997 ops/sec ±0.90% (87 runs sampled)
Decimal#toFixed x 3,020,014 ops/sec ±0.37% (89 runs sampled)
Fastest is Decimal#toFixed
BigDecimal#toString x 3,050,931 ops/sec ±0.99% (87 runs sampled)
Decimal#toString x 12,321,508 ops/sec ±0.98% (88 runs sampled)
Fastest is Decimal#toString

And with 100e12:

BigDecimal#toFixed x 2,687,047 ops/sec ±0.55% (89 runs sampled)
Decimal#toFixed x 1,734,609 ops/sec ±1.07% (92 runs sampled)
Fastest is BigDecimal#toFixed
BigDecimal#toString x 2,277,608 ops/sec ±0.89% (91 runs sampled)
Decimal#toString x 3,057,007 ops/sec ±0.70% (86 runs sampled)
Fastest is Decimal#toString

So ... much more competitive. I need to do some more checks, but I don't know if it's worthwhile doing much more on this -- the other operations are all way faster, and toFixed is a lot more important to me than toString, which is either faster or on par with decimal.js.

imirkin commented 1 year ago

With latest master (e4c3f3ee588ad4244664af353972c6a15bd5cef2), looks like this is winning (or on par) with both decimal.js and big.js (which somehow have different perf characteristics):

BigDecimal.BigDecimal x 1,012,460 ops/sec ±1.43% (91 runs sampled)
Decimal#constructor x 438,019 ops/sec ±0.50% (97 runs sampled)
Big#constructor x 677,697 ops/sec ±0.95% (91 runs sampled)
Fastest is BigDecimal.BigDecimal
BigDecimal#toFixed x 843,683 ops/sec ±0.82% (96 runs sampled)
Decimal#toFixed x 626,912 ops/sec ±1.01% (95 runs sampled)
Big#toFixed x 656,388 ops/sec ±1.74% (94 runs sampled)
Fastest is BigDecimal#toFixed
BigDecimal#toString x 2,664,587 ops/sec ±0.60% (94 runs sampled)
Decimal#toString x 2,574,290 ops/sec ±0.52% (95 runs sampled)
Big#toString x 1,353,320 ops/sec ±0.38% (95 runs sampled)
Fastest is BigDecimal#toString

For the values

let vals = [
  "98.987432987",
  "100000000000.00",
  "987344785.36",
  "1",
];