Scala specialization for numerical operation of primitive types

I wrote a function that does simple math:

def clamp(num: Double, min: Double, max: Double) = if (num < min) min else if (num > max) max else num 

It is very simple until I needed the same function with a long type. I generalized it with type parameter and specialization:

 import Ordering.Implicits._ def clamp[@specialized N: Ordering](num: N, min: N, max: N) = if (num < min) min else if (num > max) max else num 

This works, but I found that the bytecode does a lot of boxing and unpacking under the hood:

 public boolean clamp$mZc$sp(boolean num, boolean min, boolean max, Ordering<Object> evidence$1) { return Ordering.Implicits..MODULE$.infixOrderingOps(BoxesRunTime.boxToBoolean(num), evidence$1).$greater(BoxesRunTime.boxToBoolean(max)) ? max : Ordering.Implicits..MODULE$.infixOrderingOps(BoxesRunTime.boxToBoolean(num), evidence$1).$less(BoxesRunTime.boxToBoolean(min)) ? min : num; } public byte clamp$mBc$sp(byte num, byte min, byte max, Ordering<Object> evidence$1) { return Ordering.Implicits..MODULE$.infixOrderingOps(BoxesRunTime.boxToByte(num), evidence$1).$greater(BoxesRunTime.boxToByte(max)) ? max : Ordering.Implicits..MODULE$.infixOrderingOps(BoxesRunTime.boxToByte(num), evidence$1).$less(BoxesRunTime.boxToByte(min)) ? min : num; } public char clamp$mCc$sp(char num, char min, char max, Ordering<Object> evidence$1) { return Ordering.Implicits..MODULE$.infixOrderingOps(BoxesRunTime.boxToCharacter(num), evidence$1).$greater(BoxesRunTime.boxToCharacter(max)) ? max : Ordering.Implicits..MODULE$.infixOrderingOps(BoxesRunTime.boxToCharacter(num), evidence$1).$less(BoxesRunTime.boxToCharacter(min)) ? min : num; } 

Is there a better way to do general arithmetic without boxing?

+4
source share
3 answers

The spire project is definitely the place to go for high-performance numerical abstractions. All its types are specialized for ordinary types such as long, double, float, int.

Here is your method using spire typeclasses:

 import spire.algebra._ import spire.implicits._ def clamp[@specialized T:Order](a: T, min: T, max: T) = if(a < min) min else if(a > max) max else a 

And here is the specialized bytecode ( long version) extracted with: javap from scala REPL:

 public long clamp$mJc$sp(long, long, long, spire.algebra.Order<java.lang.Object>); descriptor: (JJJLspire/algebra/Order;)J flags: ACC_PUBLIC Code: stack=5, locals=8, args_size=5 0: aload 7 2: lload_1 3: lload_3 4: invokeinterface #96, 5 // InterfaceMethod spire/algebra/Order.lt$mcJ$sp:(JJ)Z 9: ifeq 16 12: lload_3 13: goto 35 16: aload 7 18: lload_1 19: lload 5 21: invokeinterface #99, 5 // InterfaceMethod spire/algebra/Order.gt$mcJ$sp:(JJ)Z 26: ifeq 34 29: lload 5 31: goto 35 34: lload_1 35: lreturn 

As you can see, it calls a long specialized version of the gt-method spire.algebra.Order, so there is no boxing.

You may also notice that the conversion from statements (<and>) to a call to the typeclass method does not appear in the code. The mechanism behind this is quite complex. See this blog post from Eric Osheim, one of the main authors of the spire.

But the bottom line is that the result is very fast, although the code is general.

+3
source

In fact, this is not a direct answer to the question, rather a comment, but it is longer than the comment, and I thought formatting would be useful.

The spire project was inspired by the requirement to be able to abstract away mathematical operations in order to be able to write generalized mathematical code with minimal overhead.

Of course, the project seems to be pretty close to the native functions in the standards , for example, the one mentioned in the previous article.

He accomplishes this by using a combination of both specialization and additional macros to rewrite the code described in this article , which I think is from Scala days 2012.

Given the results of the reference standard, I would suggest that this project can satisfy your needs.

+2
source

As I know, this cannot be done because the standard scala library uses @specialized very rarely and especially Ordering does not specialize.

And even if you still have the overhead of calling Ordering.Implicits..MODULE$.infixOrderingOps . Thus, type contexts are high-level to help with such low-level optimizations.

Thus, the only way to do generalized arithmetic operations without the overhead that I see is to generate code in some way.

0
source

All Articles