rorygraves / scalac_perf

The Scala programming language
http://www.scala-lang.org/
16 stars 3 forks source link

package object math #32

Open mkeskells opened 6 years ago

mkeskells commented 6 years ago

this contains scala aliases for java.lang.Math primitives

e.g.

  /** @group minmax */
  def min(x: Int, y: Int): Int          = java.lang.Math.min(x, y)
  /** @group minmax */
  def min(x: Long, y: Long): Long       = java.lang.Math.min(x, y)
  /** @group minmax */
  def min(x: Float, y: Float): Float    = java.lang.Math.min(x, y)
  /** @group minmax */
  def min(x: Double, y: Double): Double = java.lang.Math.min(x, y)

there is special handling for min and max etc in the compiler

generate some benchmarks to see if @inline would improve the performance, and get this to go all the way back to the call sites via RichInt etc

so

def calc(x:Int, y:Int) = x min y

should generate the same bytecode and performance as

int calc(int x, int y) {
   return Math.min(x,y);
}
mkeskells commented 6 years ago

I had a play with this a while ago - didnt check in the results, but could not get the same inlining to occur

mkeskells commented 6 years ago

run some benchmarks

[info] Benchmark                    Mode  Cnt          Score          Error  Units
[info] m.java.MathTest.min         thrpt   10  276071596.549 ´�¢ 13301280.932  ops/s
[info] m.scala.MathTest.localMin   thrpt   10  245349086.165 ´�¢ 12678119.300  ops/s
[info] m.scala.MathTest.min        thrpt   10  248087072.165 ´�¢  4477969.866  ops/s
[info] m.scala.MathTest.new2Min    thrpt   10  232759741.736 ´�¢ 44329349.640  ops/s
[info] m.scala.MathTest.new3Min    thrpt   10  267180137.855 ´�¢ 18586316.540  ops/s

the point of comparison is that scala object access slows the object access.


  // access flags 0x9
  public static Lmiketest/scala/newMath$; MODULE$

  // access flags 0x9
  public static <clinit>()V
    NEW miketest/scala/newMath$
    INVOKESPECIAL miketest/scala/newMath$.<init> ()V
    RETURN
    MAXSTACK = 1
    MAXLOCALS = 0
...
// access flags 0x2
  private <init>()V
   L0
    LINENUMBER 282 L0
    ALOAD 0
    INVOKESPECIAL java/lang/Object.<init> ()V
    ALOAD 0
    PUTSTATIC miketest/scala/newMath$.MODULE$ : Lmiketest/scala/newMath$;
   L1
    LINENUMBER 19 L1
    RETURN
   L2
    LOCALVARIABLE this Lmiketest/scala/newMath$; L0 L2 0
    MAXSTACK = 1
    MAXLOCALS = 1

could be rearranged to be more like

 // access flags 0x18
  final static Lmiketest/scala/newMath3; module

 // access flags 0x8
  static <clinit>()V
   L0
    LINENUMBER 16 L0
    NEW miketest/scala/newMath3
    DUP
    INVOKESPECIAL miketest/scala/newMath3.<init> ()V
    PUTSTATIC miketest/scala/newMath3.module : Lmiketest/scala/newMath3;
   L1
    LINENUMBER 17 L1
    RETURN
    MAXSTACK = 2
    MAXLOCALS = 0

demo and benchmark code in java

public class MathTest {
    @Benchmark
    public int min(Blackhole bh) {
        return Math.min(bh.i1, bh.i2);
    }
}

and scala

class MathTest {
  @Benchmark def min(bh: Blackhole) = math.min(bh.i1, bh.i2)
  @Benchmark def localMin(bh: Blackhole) = newMath.min(bh.i1, bh.i2)

  @Benchmark def new2Min(bh: Blackhole) = newMath2.module.min(bh.i1, bh.i2)
  @Benchmark def new3Min(bh: Blackhole) = newMath3.module.min(bh.i1, bh.i2)

}

newMath is just a local copy of the math package object (for sanity)

newMath2 and newMath3 are java approximations of the existing and improved module code

final class newMath2 {
    static newMath2 module;
    static {
        module = new newMath2();
    }
    int min (int i1, int i2) { return  Math.min(i1, i2);}
}
final class newMath3 {
    final static newMath3 module;
    static {
        module = new newMath3();
    }
    int min (int i1, int i2) { return  Math.min(i1, i2);}
}
retronym commented 6 years ago

As discussed, here's a patch to make the field final where it is safe to do so.

https://github.com/scala/scala/pull/6523

⚡ qscalac -d /tmp src/library/scala/math/package.scala && javap -c -private -cp /tmp 'scala.math.package$'
public final class scala.math.package$ {
  public static final scala.math.package$ MODULE$;

  public static {};
    Code:
       0: new           #2                  // class scala/math/package$
       3: dup
       4: invokespecial #12                 // Method "<init>":()V
       7: putstatic     #14                 // Field MODULE$:Lscala/math/package$;
      10: return
 ...
  private scala.math.package$();
    Code:
       0: aload_0
       1: invokespecial #254                // Method java/lang/Object."<init>":()V
       4: return