9
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

メソッド参照とラムダ式、どちらが速いのか

Last updated at Posted at 2018-06-02

はじめに

よく、次のようなコードを目にします。

employees.stream().map(e -> e.getName()).collect(Collectors.toList());

employees というリストの要素をすべて名前に変換したリストを作成するコード。
ごく普通のコードですが、ラムダ式ってインナークラスみたいなものだから、オーバーヘッドがあるのではないか?
こんなgetterを呼ぶだけのものであれば、

employees.stream().map(Employee::getName).collect(Collectors.toList());

の方が速いんではないか?

と思って、確かめるべく、計測してみました。
(ちょっと古いが、 Oracle JDK 1.8u131で計測しました。)

メソッド参照か、ラムダ式か

ベンチマーク

JMHを使って、それぞれのコードの1秒あたりの実行回数(スループット)を計測してみました。

public class PerformanceMethods {
    /** 10000個の要素を生成 */
    private static final List<Employee> employees = IntStream.range(0, 10000)
            .mapToObj(i -> new Employee(i, "name" + i))
            .collect(Collectors.toList());

    @Benchmark
    public void useReference() {
        employees.stream().map(Employee::getName).collect(Collectors.toList());
    }

    @Benchmark
    public void useLambda() {
        employees.stream().map(e -> e.getName()).collect(Collectors.toList());
    }
}

結果は・・・

Benchmark                         Mode  Cnt     Score     Error  Units
PerformanceMethods.useLambda     thrpt    5  5842.820 ±  65.662  ops/s
PerformanceMethods.useReference  thrpt    5  5762.353 ± 343.302  ops/s

Scoreが大きいほど処理速度が速いのですが、結果はほとんど変わらず。
繰り返し実行するなら、どちらでもよい、ということになります。

ちなみに、参考として、ラムダ式の実行の仕組みが書かれた資料を貼っておきます。
https://www.slideshare.net/miyakawataku/lambda-meets-invokedynamic

通常、インナークラスを作るとコンパイル時にクラスファイルが生成されますが(「$」付きのクラス)、ラムダ式は実行時に初期化されるため、起動が速くなるようです。
逆に取ると、ラムダ式を実行する最初のタイミングのみ、初期化が行われる分だけ遅くなる、ということになります。

気にするほどでもないと思いますが、個人的にはすっきりしているメソッド参照を使用したいと思います。

ラムダ式1回か、map2回か

他人が作ったコードのレビューをしていると、こんなコードを見かけました。

employees.stream().map(Employee::getAddress).map(Address::getPostalCode).collect(Collectors.toList());

ネストしたエンティティの値のリストを作るときに、 map を2回使って変換する方法を初めて見ました。。

確かに「個人的にはすっきりしているメソッド参照を使用したい」とは書きましたが、さすがに map 2回は効率が悪いのではないか・・・?

ということで、ラムダ式1つで map 1回で書く場合と、メソッド参照で map 2回で書く場合のパフォーマンスを計測してみました。

ベンチマーク

public class PerformanceMethods {
    private static final List<Employee> employees = IntStream.range(0, 10000)
            .mapToObj(i -> new Employee(i, "name" + i, new Address("code" + i)))
            .collect(Collectors.toList());

    @Benchmark
    public void mapOnce() {
        employees.stream()
                 .map(e -> e.getAddress().getCode())
                 .collect(Collectors.toList());
    }

    @Benchmark
    public void mapTwice() {
        employees.stream()
                 .map(Employee::getAddress)
                 .map(Address::getCode)
                 .collect(Collectors.toList());
    }
}

結果は・・・

Benchmark                     Mode  Cnt     Score      Error  Units
PerformanceMethods.mapOnce   thrpt    5  6340.454 ± 1291.055  ops/s
PerformanceMethods.mapTwice  thrpt    5  5487.546 ±  488.373  ops/s

ラムダ式( mapOnce )の方のError(処理時間のばらつき)の範囲が大きいのは気になりますが、やはり map 1回の方が速いことが分かりました。

map 1回でラムダ式を書いた方が分かりやすいし。。

結論

  • メソッド参照とラムダ式で速度の差はほとんどなし。ただし、ラムダ式は実行時に初期化されるため、最初だけ少しオーバーヘッドあり。
  • map 2回でメソッド参照を用いて値を取るよりも、map 1回でラムダ式を使って書いた方が速い。そして、読みやすい。
9
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?