Streamの配列生成は遅いの? (java)

こんなPersonクラスがあったとして

class Person
{
    String name;
    int age;

    public String getName()
    {
        return name;
    }

    public int getAge()
    {
        return age;
    }
}

List<Person> personList; として保持しているとする。

フィールド毎に配列にしたい

オブジェクトのリストを、フィールド毎に int[] や、String[] にしたい時は以下のように書ける。

先にサイズの決まった配列を用意してループで代入していく方法

String[] names = new String[personList.size()];
int[]    ages  = new int[personList.size()];
for (int i = 0; i < personList.size(); i++)
{
    Person person = personList.get(i);
    names[i] = person.getName();
    ages[i] = person.getAge();
}

Streamをつかった方法

String[] names = personList.stream().map(Person::getName).toArray(String[]::new);
int[]    ages  = personList.stream().mapToInt(Person::getAge).toArray();

できれば、後者で書きたいと思うんだけれど、処理速度とかどうなの?
と思ったので処理時間を計測してみました。

テストコード

static Random _rand = new Random();

static void testCaseArray(List<Person> personList)
{
    long processStart = System.nanoTime();

    String[] names = new String[personList.size()];
    int[] ages = new int[personList.size()];
    for (int i = 0; i < personList.size(); i++)
    {
        Person person = personList.get(i);
        names[i] = person.getName();
        ages[i] = person.getAge();
    }

    long processEnd = System.nanoTime();
    System.out.println(String.format("PROCESS_TIME: %d[ms]", (processEnd - processStart) / 1000000));
}

static void testCaseStream(List<Person> personList)
{
    long processStart = System.nanoTime();

    personList.stream().map(Person::getName).toArray(String[]::new);
    personList.stream().mapToInt(Person::getAge).toArray();

    long processEnd = System.nanoTime();
    System.out.println(String.format("PROCESS_TIME: %d[ms]", (processEnd - processStart) / 1000000));
}

public static void main(String[] args)
{
    final int size = 1000;
    //final int size = 1000000;
    //final int size = 10000000;

    List<Person> personList = new ArrayList<>();
    for (int i=0; i<size; i++)
    {
        int r = _rand.nextInt(100);
        Person person = new Person();
        person.name = "name" + r;
        person.age = r;
        personList.add(person);
    }

    System.out.println("-- case array --");
    for (int i = 0; i < 10; i++)
    {
        testCaseArray(personList);
    }

    System.out.println("-- case stream --");
    for (int i = 0; i < 10; i++)
    {
        testCaseStream(personList);
    }

}

計測

personListのサイズを1,000 から、10,000,000件にして、それぞれ10回ずつ測定してみました。

List<Person> personList; 1000件 10回ずつ測定

-- case array --
PROCESS_TIME: 0[ms]
PROCESS_TIME: 0[ms]
PROCESS_TIME: 0[ms]
PROCESS_TIME: 0[ms]
PROCESS_TIME: 0[ms]
PROCESS_TIME: 0[ms]
PROCESS_TIME: 0[ms]
PROCESS_TIME: 0[ms]
PROCESS_TIME: 0[ms]
PROCESS_TIME: 0[ms]
-- case stream --
PROCESS_TIME: 120[ms]
PROCESS_TIME: 1[ms]
PROCESS_TIME: 0[ms]
PROCESS_TIME: 0[ms]
PROCESS_TIME: 0[ms]
PROCESS_TIME: 0[ms]
PROCESS_TIME: 1[ms]
PROCESS_TIME: 0[ms]
PROCESS_TIME: 0[ms]
PROCESS_TIME: 0[ms]

streamの方は、初回だけコストが高いみたい。
メモリを多く使っているのかな?

以降は、速度面で大差はなさそう。

List<Person> personList; 100,000件 10回ずつ測定

-- case array --
PROCESS_TIME: 17[ms]
PROCESS_TIME: 4[ms]
PROCESS_TIME: 1[ms]
PROCESS_TIME: 3[ms]
PROCESS_TIME: 2[ms]
PROCESS_TIME: 3[ms]
PROCESS_TIME: 2[ms]
PROCESS_TIME: 1[ms]
PROCESS_TIME: 2[ms]
PROCESS_TIME: 2[ms]
-- case stream --
PROCESS_TIME: 118[ms]
PROCESS_TIME: 6[ms]
PROCESS_TIME: 15[ms]
PROCESS_TIME: 6[ms]
PROCESS_TIME: 8[ms]
PROCESS_TIME: 8[ms]
PROCESS_TIME: 5[ms]
PROCESS_TIME: 4[ms]
PROCESS_TIME: 6[ms]
PROCESS_TIME: 5[ms]

同様に初回だけ、コストがかかる傾向にある。
arrayの方でもそれがみられるので単純にメモリ確保に時間がかかってくるんだろうな〜と想像。

List<Person> personList; 10,000,000件 10回ずつ測定

-- case array --
PROCESS_TIME: 482[ms]
PROCESS_TIME: 92[ms]
PROCESS_TIME: 432[ms]
PROCESS_TIME: 87[ms]
PROCESS_TIME: 95[ms]
PROCESS_TIME: 339[ms]
PROCESS_TIME: 96[ms]
PROCESS_TIME: 83[ms]
PROCESS_TIME: 346[ms]
PROCESS_TIME: 86[ms]
-- case stream --
PROCESS_TIME: 313[ms]
PROCESS_TIME: 187[ms]
PROCESS_TIME: 354[ms]
PROCESS_TIME: 150[ms]
PROCESS_TIME: 155[ms]
PROCESS_TIME: 411[ms]
PROCESS_TIME: 218[ms]
PROCESS_TIME: 212[ms]
PROCESS_TIME: 150[ms]
PROCESS_TIME: 169[ms]

おそらく、GCとかも走りだして、処理速度にばらつきがでてきている模様。
ここまで来るとあまり検証にならないな。

まとめ

Streamの方が簡素に書けるけど、初期コスト
おそらく、メモリを多く使用するんだと思います。

速度がシビアな箇所では、単純な配列を先に確保して代入していく方が安定して早い事がわかった。

ちょっと泥臭いけどね・・・

コメントを残す