2

Considering a simple POJO:

class Foo {
    private String id;
    // Other fields, getters & setters ommited
}

If I have a List<Foo>, and want to produce a Stream<Integer> with all the IDs converted to int, what can I expect in terms of performance when comparing this approach:

fooList
    .stream()
    .map(foo -> Integer.parseInt(foo.getId()))

... with this one:

fooList
    .stream()
    .map(Foo::getId)
    .map(Integer::parseInt)
everton
  • 7,579
  • 2
  • 29
  • 42

1 Answers1

4

Probably the second (will have to measure), since it will "bring" the method to the call site because of the invokedynamic. Or, I could be wrong, because of the extra map operation and the infrastructure needed to handle that. Will update the post with some jmh results.

Indeed (according to the tests I have), the method reference is faster:

Benchmark              Mode  Cnt  Score   Error  Units
MyBenchmark.doubleMap  avgt   20  3.973 ± 0.057  ms/op
MyBenchmark.singleMap  avgt   20  6.222 ± 2.216  ms/op

And here is the code :

@State(Scope.Thread)
public class MyBenchmark {

    private List<Foo> singleMapList = new ArrayList<>();

    private List<Foo> doubleMapList = new ArrayList<>();

    @Benchmark
    @BenchmarkMode(Mode.AverageTime)
    @OutputTimeUnit(TimeUnit.MILLISECONDS)
    public List<Integer> singleMap() {
        return singleMapList.stream().map(foo ->     Integer.parseInt(foo.getId())).collect(Collectors.toList());
    }

    @Benchmark
    @BenchmarkMode(Mode.AverageTime)
    @OutputTimeUnit(TimeUnit.MILLISECONDS)
    public List<Integer> doubleMap() {
        return doubleMapList.stream().map(Foo::getId).map(Integer::parseInt).collect(Collectors.toList());
}

    @Setup
    public void setup() {
        for (int i = 0; i < 100_000; i++) {
            singleMapList.add(new Foo("" + i));
            doubleMapList.add(new Foo("" + i));
        }
    }

     public static void main(String[] args) throws RunnerException {
        Options opt = new OptionsBuilder().include(MyBenchmark.class.getSimpleName()).forks(1).build();
        new Runner(opt).run();
    }
}
Eugene
  • 117,005
  • 15
  • 201
  • 306
  • This is interesting, designing these stream transformations in a pipeline kind of way seems to be the good practice. Thanks! – everton Nov 09 '16 at 12:54
  • 1
    I have no idea what “*since it will "bring" the method to the call site because of the invokedynamic*” is supposed to mean. I don’t see any reason for a significant performance difference. Note that in your benchmark result, the error has the same magnitude as the measured difference between the two results. – Holger Nov 09 '16 at 18:35