fixnum icon indicating copy to clipboard operation
fixnum copied to clipboard

fixnum classes could be much more performant when targeting the Dart VM

Open jamesderlin opened this issue 2 years ago • 5 comments

Since Dart conditional imports/exports are a thing, has there been much consideration to making package:fixnum offer different implementations of Int32/Int64 for the Dart VM than for the web? For the VM, they could be mostly simple wrappers around int.

Currently anyone considering package:fixnum has to make a tradeoff between portability and a significant difference in performance. I made a quick (but very crude) prototype that replaced the internals of Int64, and it seems ~3x faster for the benchmarks I ran. (It still seems ~3x slower than using int directly, presumably due to all of the unboxing/boxing, but that's better than the ~9x difference I see with the current implementation.)

jamesderlin avatar Apr 22 '22 07:04 jamesderlin

I'm not certain of the relative usage of package:fixnum, and whether we want to consider it a paved path for in terms of our numeric types. This library is used by grpc, profobuf, built_value, intl, and others (https://pub.dev/packages?q=dependency%3Afixnum), though some of those uses may be from when Dart had arbitrary precision integers.

cc @mraleph for the performance questions, and @leafpetersen for the supported types question

devoncarew avatar Apr 26 '22 15:04 devoncarew

I think it would be great to find a way to make fixnum both portable and performant. It's also causing some performance issues and bloat in the protobuf's.

I have some ideas on how it could be made really performant on the VM with few minor tweaks, but it also needs some language support, e.g. I would like to have a way to allow VM loose identity of Int64 wrapper.

mraleph avatar Apr 26 '22 15:04 mraleph

I think a conditional import of a better VM version might be a good idea if we keep the current interface.

Why might we change the interface? Some customers using protobufs want care-free handling of int64 and uint64 values. What they mostly want is to the appropriate sign with toString() and division. I 'fixed' the former by adding a toStringUnsigned() method, but that does not help with string interpolation, division, for formatting with NumberFormat. I have toyed with the idea of making Int64 a 64-and-a-half bit value that supports both signed and unsigned. We could support an 'isUnsigned' bit at almost no cost in the current Int64 class, but not if it is a simple wrapper of the Dart VM 64-bit int. So perhaps it is a good thing I never moved on the idea.

@mraleph I see very little Int64 arithmetic in one of the big apps I study. It is all 'I/O' - data copying and formatting for display. I'm interested to hear about the performance issues you know of. To the degree that Int64 operations show up at all in web profiling, it is conversion between Int64 and String during protobuf serialization. Binary protobuf conversion could be made faster by adding Int64 methods that incorporate the conversion, directly between UInt8List <-> Int64, rather than performing many Int64 operations to process the bytes. @osa1 I'm happy to share my accumulated thoughts on this if you decide to look at improving the Int64 operations in the protobuf code.

For code where actual arithmetic is actually important: I have had some success in vector_math and dart:html with staging the operations into clear unwrap-compute-wrap steps, where 'compute' is a static function. This allows the outer method to be inlined to enable scalar replacement, leaving calls to the 'compute' method. Since Int64 operations promote arguments from int and Int32, perhaps that could be supported by inlining the 'promotion' part. Arguments usually have a static type known well enough to specialize the 'promotion'.

rakudrama avatar May 03 '22 05:05 rakudrama

@rakudrama https://github.com/dart-lang/sdk/issues/44175 has example of code that needs reasonable Int64 performance. That being said I agree that I have never seen this problem internally so far.

mraleph avatar May 03 '22 12:05 mraleph

If we get views, it may be technically possible to retain the current API, and make the VM Int64 a view on an int. That gives zero overhead, but has different dynamic behavior than an actual wrapper class if you do casts at runtime. (If you don't cast, and always use FixNum types as typed, it's probably going to be fine.)

lrhn avatar Oct 12 '22 15:10 lrhn