Details
-
Type:
Defect
-
Status:
Open
-
Priority:
Major
-
Resolution: Unresolved
-
Affects Version/s: Release 1.3
-
Fix Version/s: None
-
Component/s: None
-
Labels:None
Description
Found this while profiling some performance-critical code.
Consider the following Clojure function:
(defn test-case ^double [^long i ^double d1 ^double d2] (case (int i) 0 d1 d2))
Current Clojure 1.3 snapshot compiles it to:
public final double invokePrim(long, double, double) throws java.lang.Exception; Code: 0: lload_1 1: invokestatic #67; //Method clojure/lang/RT.intCast:(J)I 4: istore 7 6: iload 7 8: i2l 9: invokestatic #73; //Method clojure/lang/Numbers.num:(J)Ljava/lang/Number; 12: invokestatic #79; //Method clojure/lang/Util.hash:(Ljava/lang/Object;)I 15: iconst_0 16: ishr 17: iconst_1 18: iand 19: tableswitch{ //0 to 0 0: 36; default: 58 } 36: iload 7 38: i2l 39: invokestatic #73; //Method clojure/lang/Numbers.num:(J)Ljava/lang/Number; 42: getstatic #45; //Field const__3:Ljava/lang/Object; 45: invokestatic #83; //Method clojure/lang/Util.equals:(Ljava/lang/Object;Ljava/lang/Object;)Z 48: ifeq 58 51: dload_3 52: invokestatic #88; //Method java/lang/Double.valueOf:(D)Ljava/lang/Double; 55: goto 63 58: dload 5 60: invokestatic #88; //Method java/lang/Double.valueOf:(D)Ljava/lang/Double; 63: checkcast #92; //class java/lang/Number 66: invokevirtual #96; //Method java/lang/Number.doubleValue:()D 69: dreturn }
This bytecode contains boxing of primitives (calls to clojure/lang/Numbers.num and java/lang/Double.valueOf) and calls to clojure/lang/Util.hash and clojure/lang/Util.equals that does not seem necessary.
At 60-66 primitive double is boxed into Double only to be converted back into primitive.
The equivalent Java code compiles to much simpler and faster bytecode:
public double testCase(long, double, double); Code: 0: lload_1 1: l2i 2: lookupswitch{ //1 0: 20; default: 22 } 20: dload_3 21: dreturn 22: dload 5 24: dreturn }
Improved via patch on
CLJ-426.(defn test-case ^double [^long i ^double d1 ^double d2] (case (int i) 0 d1 d2))now emits as
0 lload_1 [i] 1 invokestatic clojure.lang.RT.intCast(long) : int [67] 4 istore 7 [G__7903] // let-bound expression 6 iload 7 [G__7903] 8 tableswitch default: 32 case 0: 28 28 dload_3 [d2] 29 goto 34 32 dload 5 [arg2] 34 dreturnor if the int cast of the expression is omitted:
0 lload_1 [i] 1 lstore 7 [G__7903] // let-bound expression 3 lload 7 [G__7903] 5 l2i 6 tableswitch default: 35 case 0: 24 24 lconst_0 // match, verify long expr wasn't truncated 25 lload 7 [G__7903] 27 lcmp 28 ifne 35 31 dload_3 [d2] 32 goto 37 35 dload 5 [arg2] 37 dreturnCLJ-426.(defn test-case ^double [^long i ^double d1 ^double d2] (case (int i) 0 d1 d2))0 lload_1 [i] 1 invokestatic clojure.lang.RT.intCast(long) : int [67] 4 istore 7 [G__7903] // let-bound expression 6 iload 7 [G__7903] 8 tableswitch default: 32 case 0: 28 28 dload_3 [d2] 29 goto 34 32 dload 5 [arg2] 34 dreturn0 lload_1 [i] 1 lstore 7 [G__7903] // let-bound expression 3 lload 7 [G__7903] 5 l2i 6 tableswitch default: 35 case 0: 24 24 lconst_0 // match, verify long expr wasn't truncated 25 lload 7 [G__7903] 27 lcmp 28 ifne 35 31 dload_3 [d2] 32 goto 37 35 dload 5 [arg2] 37 dreturn