Hacker News new | past | comments | ask | show | jobs | submit login
How Scala Compiles to Java (iamchrislewis.tumblr.com)
73 points by fogus on Sept 21, 2010 | hide | past | favorite | 23 comments



Interesting post.

Another post that I'd like to read would be: How Many Objects Does Scala Allocate (while executing some random code).

In my Java shop, we try to write ultra-efficient code. Avoiding the creation of numerous small objects in critical sections is one of the best ways to achieve that.

I'd like to start a couple projects in scala or clojure but I'm afraid that the simplicity and expressiveness that these languages bring come at the cost of performance (or at least on control on what the JVM does).


I'm hacking scala for android and in my experience scala is really awful with allocations. When using idiomatic scala in a game loop the gc kicks in 10 times a seconds, making the app unusable. There are a lot of "invisible" allocations, i.e. allocations that happen even when you don't use "new".

But even without scala-idioms scala is so much nicer than java that I never want to go back.


Nice. Can you go over more what scala idioms you mean and how you replaced them with java/imperative? idioms. A friend and I are just starting coding a small android app in Scala for learning and are always on the look out for info. Any resources you can provide would be awesome as well (I've seen you need to take steps to reduce scala-library bloat). Cheers!


Hi, sorry for the late reply: I have a (short) summary here:

http://teh.gitblogs.com/post/how-to-avoid-allocations-when-u...

For reducing the library size see e.g. http://github.com/teh/androidmake

I've seen others do similar things, you'll need to google for their solutions.


At least you can write java-style code (or call java seamlessly) for whatever is performance-critical. I agree that idiomatic Scala can be too slow in some cases (it should be faster due to static typing than Clojure wherever methods aren't generic, or are specialized).


>should be faster due to static typing than Clojure

Uh, why? Are all object types known at runtime in Clojure? I bet they are. And this is exactly what Hotspot was designed to make fast [1].

[1] http://www.strongtalk.org


Unfortunately "as good or better" performance is not realized yet, at least not at theThe Computer Language Benchmarks Game[1]. More code, more time, more memory required by Clojure than Scala, to solve the same problems.

However the Clojure implementations on the site are still quite young, so some improvement may be possible.

[1] http://shootout.alioth.debian.org/u32/benchmark.php?test=all...


My point was that it isn't the typing that makes it fast. Hotspot is based on the idea that the type is known at runtime.

The Shootout probably wont show good results for this because it takes a little bit for Hotspot to get the most out of it. If you look at the Mandelbrot demo in Strongtalk it takes about 3 full runs to get the best speed.


Either I don't understand, or you are mistaken. Clojure is significantly slower than Java and Scala in the shootout. Presumably Hotspot kicks in, or doesn't, more or less equivalently in all 3 cases. The dynamic languages (Clojure, Groovy, JRuby) are all slower than the statically typed languages (Java, Scala); what else would cause that?


This raises an interesting question for igouy: there are dozens of jvm knobs you can pull to optimize each benchmark: "-server", "XX:MaxInlineSize", the many "XX" options to play with GC etc. I'm assuming that the same settings would be used for java, scala, and groovy for any single benchmark, but are these switches tuned for that particular benchmark, or the same used for all benchmarks?


>what else would cause that?

How much work the code that they generate does. Clojure is mostly pure right? If you have, e.g. a btree and you change some nodes the old notes won't get deleted, a new tree will be created where some of the nodes point to the still valid old ones and some point to your changes. That's more work then just mutating the variables.


Clojure is using a lot more memory, any idea why that is?


Immutable data structures?


Why and where (which idioms) is this the case? Is this due to type erasure on generics or something else? I ask this as this is the sort of knowledge that remains implicit amongst the experienced.

p.s. i'm new to scala (and using it on app engine and android) but have extensive F# and decent haskell and ocaml experience and have found stuff like hyperpolyglot useful but this type of knowledge is not readily found.


Implicit wrappers have long been an issue for this sort of optimisation, although I was pleased to read that the new JVM escape analysis feature seems to deal with that very effectively:

http://www.decodified.com/scala/2010/08/27/scala-rich-wrappi...

As other comments have noted you can generally degrade to writing near-Java code and the results will be basically the same bytecode. I'm sure there are exceptions but if you want to, you can avoid object creation. Implicit conversions are one of those cases which will not be called out as a warning or similar, though, so something to be careful of.


In my Java shop, we try to write ultra-efficient code. Avoiding the creation of numerous small objects in critical sections is one of the best ways to achieve that.

I don't know anything about Java, but the Objective-C GC recycles old allocations to avoid this exact problem. For example

    for (int i = 0; i < 10000; i++)
    {
    	Foo *foo = [[Foo alloc] init];
    }
Instead of allocating memory for foo 10000 times, older allocations are kept in a pool and reused for subsequent iterations. So the actual cost of -alloc is just a memset (i.e. nothing). Java's GCs are much more mature, so I would assume they have the same optimisation.


>In my Java shop, we try to write ultra-efficient code. Avoiding the creation of numerous small objects in critical sections is one of the best ways to achieve that.

I'd like to know why you think creating numerous small objects is an issue... Short lived small objects should be negligible to an application's performance.


The great part about the JVM is that you can chose the language which fits the problem. If something needs the fine-grain control of Java, use Java. If another part would benefit greatly from the concise and expressive Scala idioms, use Scala. Eventually it's all going to be broken down into several JARs which are passed along to the next guy, hidden by bytecode as to which JAR was written in which language.


> In my Java shop, we try to write ultra-efficient code.

Are you sure you should be doing that? As Knuth said, "premature optimization is the root of all evil".


I would suspect the poster has already weighed the cost vs. benefit of writing efficient code. Many domains do require careful attention to performance.


I should have precised that we only optimize modules where performance is critical.

Otherwise, our #1 priority is that code should be beautiful (concise, readable).

A nice side effect is that sometimes "nicer" also means more efficient (the JVM likes small methods) :)


Very cool, but as someone unfamiliar with scala (and familiar with java) I would really have liked to see java code for those 6 class files. Obviously they would have to have been approximations, but seeing the 6 raw java source files vs. the 1 scala (and the same .class outputs) I feel would have made a very nice comparison. It also would do away with any ambiguity in the verbal description of the congruencies between the two.


This is approximately how the first code block in the OP would look like in Java 5:

    import scala.Function1;
    import scala.List;
    import scala.runtime.RichInt;

    class App$ {
        public static final App$ MODULE$ = new App$();
    
        public boolean isEven(int i) {
            return i % 2 == 0;
        }
    
        public boolean isOdd(int i) {
            return i % 2 == 1;
        }
    
        public void main(String[] args) {
            final List<Integer> n = (new RichInt(1)).to(10).toList();
            n.filter(new Function1<Integer, Boolean>() {
                public Boolean apply(final Integer x) {
                    return isEven(x);
                }
            }).flatMap(new Function1<Integer, List<Integer>>() {
                public List<Integer> apply(final Integer i) {
                    return n.filter(new Function1<Integer, Boolean>() {
                        public Boolean apply(final Integer x) {
                            return isOdd(x);
                        }
                    }).map(new Function1<Integer, Integer>() {
                        public Integer apply(final Integer j) {
                            return i * j;
                        }
                    });
                }
            });
        }
    }

    class App {
        public static boolean isEven(int i) {
            return App$.MODULE$.isEven(i);
        }
    
        public static boolean isOdd(int i) {
            return App$.MODULE$.isOdd(i);
        }
    
        public static void main(String[] args) {
            App$.MODULE$.main(args);
        }
    }
Note that, although anonymous inner classes are valid syntax in Java 5, even in Java 5 they are compiled down to separate class files. Hence 2 classes + 4 anonymous inner classes = 6 class files total.

(The code is only approximate. I haven't tried to compile it, so apologies for any errors.)




Consider applying for YC's W25 batch! Applications are open till Nov 12.

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: