scalac_perf
scalac_perf copied to clipboard
package object math
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);
}
I had a play with this a while ago - didnt check in the results, but could not get the same inlining to occur
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);}
}
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