tc39 / proposal-decimal

Built-in exact decimal numbers for JavaScript
http://tc39.es/proposal-decimal/
482 stars 17 forks source link

How fast can decimals be if implemented in userland? #78

Open sffc opened 1 year ago

sffc commented 1 year ago

It would be compelling for the Decimal proposal if it meant that calculations in decimal space were faster than is achievable using plain JS code (or perhaps WASM code).

Do you have data on whether either decimal128 or BigDecimal can be implemented efficiently in JS code, and what the performance numbers look like versus an implementation in C/Rust?

jessealama commented 1 year ago

I'm not aware of any userland implementations of Decimal128 in JS (not even partially, for, say, addition and multiplication only). One route would be to take a C++ library for Decimal128 (say, Bloomberg's BDE), compile it to Wasm, and hook that into JS.

BigDecimal is much easier to test since there are a few good userland libraries out there that do pretty much everything we could want.

In other words, getting benchmarks might be a bit tricky (because of Decimal128).

In addition to measuring speed, I'd also be interested in getting some numbers on memory consumption. One worry about arbitrary-precision decimals is that, when faced with complex calculations, the number of digits involved might unintentionally get big, quickly, unless one were careful to always limit the precision of the operations to something reasonably small.

jessealama commented 1 year ago

The following is not a knock-down argument (it's not based on any data), but just thinking about the ways that one could represent decimals in JavaScript, the only two options that occur to me for the underlying data are:

  1. digit strings ("123.456")
  2. BigInts (e.g., two of them, one for before and one for after the decimal point).

I'd be surprised if working with digit strings could possibly be as fast as, say, a native Decimal128 implementation. One would work digit by digit through the string.

michaelficarra commented 12 months ago

@jessealama By manually interning (hoisting all "constant" constructions of decimals to the top of the program), you would avoid parsing the string more than once. And parsing the string once is ~equivalent to the engine parsing the decimal out of the source text as a literal, so there's not much harm there other than the footgun of having to do the interning or you get bad perf.

As far as perf compared to a wasm or JS library impl, a native impl could take advantage of hardware acceleration that has become available in recent processors. That said, Decimal128 was designed in a way that a userland (wasm) bit-twiddling implementation would be decently performant anyway. So it may not make all that much of a difference.

sffc commented 11 months ago

Basically what I'm asking is to benchmark a little script that takes a decimal performs some operation on it in pure Rust/C++ and then across the WASM boundary.

You should also note the code size of this WASM file. If you get a file that is several MB in size after you perform code size optimizations (i.e. building your Rust code with opt-level=s, #![no_std], etc) that could also be a motivator.

Here are some 128-bit decimal libraries in Rust; not sure if they are what you are looking for:

https://docs.rs/dec/latest/dec/struct.Decimal128.html (this one wraps decNumber which is a C library)

https://docs.rs/rust_decimal/latest/rust_decimal/struct.Decimal.html (this one is in pure Rust)