Skip to content

RFE: inline methods for spire algebra typeclass instances #1309

@erikerlandson

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

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions