Skip to content

RFE: inline methods for spire algebra typeclass instances #1309

Description

@erikerlandson

I believe it could be an improvement, especially for scala-3, to provide inline definitions of algebra typeclass methods.

To try and illustrate, consider this example. Here I will define a couple of variations on an add function, and also a definition of additive semigroup whose plus method is inline:

object defs:
    import algebra.ring.*
    // an in-line function using semigroup typeclass
    inline def add[V](x: V, y: V)(using alg: AdditiveSemigroup[V]): V =
        alg.plus(x, y)
    // a non-inline version of same function
    def add2[V](x: V, y: V)(using alg: AdditiveSemigroup[V]): V =
        alg.plus(x, y)
    // a typeclass with inline 'plus' method
    given AdditiveSemigroup[Double] with
        inline def plus(x: Double, y: Double): Double =
            x + y
    val x = 1.0

To compare them, here are two equivalent blocks of code, but one uses the standard Spire algebras and the other uses the inline typeclass above:

object useSpire:
    import spire.implicits.*
    import defs.*
    val z = add(x, 1.0)

object useInline:
    import defs.*
    import defs.given
    val z = add(x, 1.0)

If you compare the resulting bytecode, I believe the typeclass with inline methods gives a more efficient result, using the inline add function:

using standard spire algebra:

  public static {};
    Code:
       0: new           #2                  // class coulomb/useSpire$
       3: dup
       4: invokespecial #18                 // Method "<init>":()V
       7: putstatic     #20                 // Field MODULE$:Lcoulomb/useSpire$;
      10: getstatic     #25                 // Field spire/implicits$.MODULE$:Lspire/implicits$;
      13: invokevirtual #29                 // Method spire/implicits$.DoubleAlgebra:()Lalgebra/ring/Field;
      16: getstatic     #34                 // Field coulomb/defs$.MODULE$:Lcoulomb/defs$;
      19: invokevirtual #38                 // Method coulomb/defs$.x:()D
      22: invokestatic  #44                 // Method scala/runtime/BoxesRunTime.boxToDouble:(D)Ljava/lang/Double;
      25: dconst_1
      26: invokestatic  #44                 // Method scala/runtime/BoxesRunTime.boxToDouble:(D)Ljava/lang/Double;
      29: invokeinterface #50,  3           // InterfaceMethod algebra/ring/Field.plus:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
      34: invokestatic  #54                 // Method scala/runtime/BoxesRunTime.unboxToDouble:(Ljava/lang/Object;)D
      37: putstatic     #56                 // Field z:D
      40: return

Using algebra with inline method:

  public static {};
    Code:
       0: new           #2                  // class coulomb/useInline$
       3: dup
       4: invokespecial #18                 // Method "<init>":()V
       7: putstatic     #20                 // Field MODULE$:Lcoulomb/useInline$;
      10: getstatic     #25                 // Field coulomb/defs$.MODULE$:Lcoulomb/defs$;
      13: invokevirtual #29                 // Method coulomb/defs$.x:()D
      16: dconst_1
      17: dadd
      18: putstatic     #31                 // Field z:D
      21: return

If both arguments are literal constants, the inlined improvements are even more pronounced:

object useInline:
    import defs.*
    import defs.given
    val z = add(1.0, 1.0)  // two constant args

scala-3 compiles it down to a single constant value in the byte-code:

  public static {};
    Code:
       0: new           #2                  // class coulomb/useInline$
       3: dup
       4: invokespecial #18                 // Method "<init>":()V
       7: putstatic     #20                 // Field MODULE$:Lcoulomb/useInline$;
      10: ldc2_w        #21                 // double 2.0d
      13: putstatic     #24                 // Field z:D
      16: return

Lastly, if one uses the non-inline add2 function, both versions of the typeclass yield the same bytecode, which looks like this:

object useInline:
    import defs.*
    import defs.given
    val z = add2(x, 1.0)
  public static {};
    Code:
       0: new           #2                  // class coulomb/useInline$
       3: dup
       4: invokespecial #23                 // Method "<init>":()V
       7: putstatic     #25                 // Field MODULE$:Lcoulomb/useInline$;
      10: getstatic     #30                 // Field coulomb/defs$.MODULE$:Lcoulomb/defs$;
      13: getstatic     #30                 // Field coulomb/defs$.MODULE$:Lcoulomb/defs$;
      16: invokevirtual #34                 // Method coulomb/defs$.x:()D
      19: invokestatic  #40                 // Method scala/runtime/BoxesRunTime.boxToDouble:(D)Ljava/lang/Double;
      22: dconst_1
      23: invokestatic  #40                 // Method scala/runtime/BoxesRunTime.boxToDouble:(D)Ljava/lang/Double;
      26: getstatic     #43                 // Field coulomb/defs$given_AdditiveSemigroup_Double$.MODULE$:Lcoulomb/defs$given_AdditiveSemigroup_Double$;
      29: invokevirtual #47                 // Method coulomb/defs$.add2:(Ljava/lang/Object;Ljava/lang/Object;Lalgebra/ring/AdditiveSemigroup;)Ljava/lang/Object;
      32: invokestatic  #51                 // Method scala/runtime/BoxesRunTime.unboxToDouble:(Ljava/lang/Object;)D
      35: putstatic     #53                 // Field z:D
      38: return

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions