📈

JavaのfindFirstとfindAnyの違い

2024/06/07に公開

あまり使うことのなかったfindFirstfindAnyを調べたのでメモ

findFirstとは

findFirstメソッドは、Streamから最初の要素を返すために使用される。
順序付けされている状態であれば、findFirstは常に最初の要素を返す。

Main.java
List<String> names = Arrays.asList("Test1", "Test2", "Test3", "Test4");
Optional<String> firstName = names.stream().findFirst();
firstName.ifPresent(System.out::println); //→Test1

findAnyとは

findAnyメソッドは、ストリームから任意の要素を返す。
特に並列でストリームを回しているときに性能が良くなるらしい。。。

Main.java
List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David");
Optional<String> anyName = names.parallelStream().findAny();
anyName.ifPresent(System.out::println);

findAnyいつ使うの?

性能的に考えてもfindFirstで最初が返されるのであれば
感覚的にfindFirstの方が速そう、、、

なので試してみた。

前提

parallelStream自体がマルチコアプロセッサを活かして動く形なので以降の処理結果は
CPUに依存する。
XeonなどのマルチコアゴリゴリのCPU等であれば処理結果変わらない可能性がある。

parallelStreamでのfindFirstとfindAnyの比較

Main.java
import java.util.*;
import java.util.stream.*;

public class Main {
    public static void main(String[] args) {
        for(int i = 1; i < 11; i++){
            execute(i);
        }
    }
    
    
    private static void execute(int index){
        System.out.println(index + "回目");

        List<Integer> largeList = IntStream.range(0, 1000000).boxed().collect(Collectors.toList());

        // findFirstの計測
        long startTime1 = System.nanoTime();
        Optional<Integer> first = largeList.parallelStream().findFirst();
        long endTime1 = System.nanoTime();
        System.out.println("findFirst took :" + (endTime1 - startTime1) + " ns");

        // findAnyの計測
        long startTime2 = System.nanoTime();
        Optional<Integer> any = largeList.parallelStream().findAny();
        long endTime2 = System.nanoTime();
        System.out.println("findAny took   :" + (endTime2 - startTime2) + " ns");
    }
}

log
1回目
findFirst took :533346 ns
findAny took   :76237 ns
2回目
findFirst took :349193 ns
findAny took   :70898 ns
3回目
findFirst took :163967 ns
findAny took   :100547 ns
4回目
findFirst took :140695 ns
findAny took   :106406 ns
5回目
findFirst took :146328 ns
findAny took   :74622 ns
6回目
findFirst took :293930 ns
findAny took   :95320 ns
7回目
findFirst took :141319 ns
findAny took   :51707 ns
8回目
findFirst took :147466 ns
findAny took   :63520 ns
9回目
findFirst took :138300 ns
findAny took   :283056 ns
10回目
findFirst took :163080 ns
findAny took   :91353 ns

同じように要素を少なく(10個)にしてみた結果

Main.java
1回目
findFirst took :132483 ns
findAny took   :58254 ns
2回目
findFirst took :112732 ns
findAny took   :53424 ns
3回目
findFirst took :108216 ns
findAny took   :73001 ns
4回目
findFirst took :118181 ns
findAny took   :52879 ns
5回目
findFirst took :117647 ns
findAny took   :51617 ns
6回目
findFirst took :124880 ns
findAny took   :63088 ns
7回目
findFirst took :126601 ns
findAny took   :60481 ns
8回目
findFirst took :124372 ns
findAny took   :59836 ns
9回目
findFirst took :120962 ns
findAny took   :50714 ns
10回目
findFirst took :107710 ns
findAny took   :51197 ns

parallelStreamでのfindFirstとfindAnyの結果

結果的にはparallelStream同士ではfindFirstの方がかなり速かった。

parallelStreamとstreamの場合で比べてみたらどうなるのか気になったので
試してみた。

parallelStreamとStreamでのfindFirstとfindAnyの比較

Main.java
import java.util.*;
import java.util.stream.*;

public class Main {
    public static void main(String[] args) {
        for(int i = 1; i < 11; i++){
            execute(i);
        }
    }
    
    
    private static void execute(int index){
        System.out.println(index + "回目");

        List<Integer> largeList = IntStream.range(0, 100000).boxed().collect(Collectors.toList());

        // findFirstの計測
        long startTime1 = System.nanoTime();
        Optional<Integer> first = largeList.stream().findFirst();
        long endTime1 = System.nanoTime();
        System.out.println("findFirst took :" + (endTime1 - startTime1) + " ns");

        // findAnyの計測
        long startTime2 = System.nanoTime();
        Optional<Integer> any = largeList.parallelStream().findAny();
        long endTime2 = System.nanoTime();
        System.out.println("findAny took   :" + (endTime2 - startTime2) + " ns");
    }
}

log
1回目
findFirst took :16188 ns
findAny took   :85091 ns
2回目
findFirst took :15263 ns
findAny took   :77307 ns
3回目
findFirst took :15275 ns
findAny took   :108210 ns
4回目
findFirst took :11231 ns
findAny took   :62859 ns
5回目
findFirst took :11406 ns
findAny took   :60593 ns
6回目
findFirst took :11201 ns
findAny took   :60128 ns
7回目
findFirst took :11290 ns
findAny took   :59533 ns
8回目
findFirst took :17196 ns
findAny took   :1116963 ns
9回目
findFirst took :15139 ns
findAny took   :77320 ns
10回目
findFirst took :16299 ns
findAny took   :80137 ns

こちらも同じように要素を少なく(10個)してみた結果

log
1回目
findFirst took :11960 ns
findAny took   :65212 ns
2回目
findFirst took :10200 ns
findAny took   :61745 ns
3回目
findFirst took :8921 ns
findAny took   :55540 ns
4回目
findFirst took :8800 ns
findAny took   :53469 ns
5回目
findFirst took :8788 ns
findAny took   :54545 ns
6回目
findFirst took :10299 ns
findAny took   :54831 ns
7回目
findFirst took :9144 ns
findAny took   :57409 ns
8回目
findFirst took :8523 ns
findAny took   :52583 ns
9回目
findFirst took :8659 ns
findAny took   :53152 ns
10回目
findFirst took :9056 ns
findAny took   :58739 ns

parallelStreamとstreamでのfindFirstとfindAnyの結果

結果的には、Streamにした状態のfindFirstが速かった。

まとめ

parallelStreamを使用する場合はfindAny
streamで良い場合は、findFirstの方がよさげ。

ただ、parallelStreamが必要なレベルの重たい処理を行ったことがないため、
parallelStreamfindFirstの方がよい場合もあるのかも

Discussion