ラムダ式で本領を発揮する関数型インターフェースとStream APIの基礎知識Java 8はラムダ式でここまで変わる(3)(3/3 ページ)

» 2014年04月30日 18時00分 公開
[長谷川智之株式会社ビーブレイクシステムズ]
前のページへ 1|2|3       

Stream APIの並列処理はマルチコアの恩恵を受けて高速化できるのか

 Stream APIの特徴の1つに並列処理の実装を容易にできることがあります。現在、多くのコンピューターのCPUは複数のコアを持っています。そして、それらの各コアが処理を同時に行うことで負荷を分散し処理速度を上げています。

 しかし、単純なforループのように処理を1つのスレッドで順々に行う直列処理の場合、それらの恩恵を受けることができませんでした。一方で、並列処理を使えるようになれば、複数コアのCPUの利点を生かしたパフォーマンスの改善が期待できるようになります。

streamメソッドをparallelStreamメソッドに書き換えるだけ

 では、実際にStream APIを使って並列処理の実装を行ってみましょう。先ほどのStream APIサンプルでは並列処理ではなく直列処理を行っています。並列処理にするには、このCollectionのstreamメソッドをparallelStreamメソッドに書き換えるだけで並列処理に変更できます。

 今回のサンプルを並列処理に変えると、次のようになります。

  1. int maxSalesAmount = salesDataList.parallelStream() streamparallelStreamに変更
  2. .filter(salesData -> salesData.getLocation().equals("東京"))
  3. .mapToInt(salesData -> salesData.getSalesAmount())
  4. .max()
  5. .getAsInt();

実際に試してみた

 マルチコアのCPUのマシンで並列処理を行うと本当に効果があるのか実際に試してみましょう。今回は上記の処理に2014年1月以前という条件を加えて、次の3パターンで5回実行してみました。

  1. forループで行った場合
  2. streamメソッドで行った場合
  3. parallelStreamメソッドで行った場合

 実装は次のようになります。

  1. public static void main(String[] args) {
  2. // 2014年1月末日を取得
  3. Calendar calendar = Calendar.getInstance();
  4. calendar.clear();
  5. calendar.set(2014, 1, 31);
  6. Date endDateOfJanuary = calendar.getTime();
  7. // テストデータの生成
  8. List<SalesData> salesDataList = SalesData.createSalesDateList();
  9. // 5回実行
  10. for (int i = 1; i <= 5; i++) {
  11. System.out.println(i + "回目 ------------------------------");
  12. // forループの場合
  13. int maxSalesAmount = -1;
  14. long start = System.currentTimeMillis();
  15. for (SalesData salesData : salesDataList) {
  16. if (salesData.getLocation().equals("東京")) {
  17. if (salesData.getDate().compareTo(endDateOfJanuary) <= 0) {
  18. if (maxSalesAmount < 0) {
  19. maxSalesAmount = salesData.getSalesAmount();
  20. } else if (maxSalesAmount < salesData.getSalesAmount()) {
  21. maxSalesAmount = salesData.getSalesAmount();
  22. }
  23. }
  24. }
  25. }
  26. long end = System.currentTimeMillis();
  27. System.out.println("forループ: time=" + (end - start) + "ms");
  28. // streamメソッドの場合
  29. maxSalesAmount = -1;
  30. start = System.currentTimeMillis();
  31. maxSalesAmount = salesDataList.stream()
  32. .filter(salesData -> salesData.getLocation().equals("東京"))
  33. .filter(salesData -> salesData.getDate().compareTo(endDateOfJanuary) <= 0)
  34. .mapToInt(salesData -> salesData.getSalesAmount())
  35. .max()
  36. .getAsInt();
  37. end = System.currentTimeMillis();
  38. System.out.println("stream: time=" + (end - start) + "ms");
  39. // parallelStreamメソッドの場合
  40. maxSalesAmount = -1;
  41. start = System.currentTimeMillis();
  42. maxSalesAmount = salesDataList.parallelStream()
  43. .filter(salesData -> salesData.getLocation().equals("東京"))
  44. .filter(salesData -> salesData.getDate().compareTo(endDateOfJanuary) <= 0)
  45. .mapToInt(salesData -> salesData.getSalesAmount())
  46. .max()
  47. .getAsInt();
  48. end = System.currentTimeMillis();
  49. System.out.println("parallelStream: time=" + (end - start) + "ms");
  50. }
  51. }

 これを4コアのCPUのマシンで実行したところ、次の結果になりました。

1回目 ------------------------------
forループ: time=282ms
stream time=420ms
parallelStream time=322ms
2回目 ------------------------------
forループ: time=536ms
stream time=563ms
parallelStream time=162ms
3回目 ------------------------------
forループ: time=483ms
stream time=683ms
parallelStream time=154ms
4回目 ------------------------------
forループ: time=485ms
stream time=689ms
parallelStream time=150ms
5回目 ------------------------------
forループ: time=374ms
stream time=559ms
parallelStream time=149ms

 この結果を見るとStream APIの並列処理(parallelStream)を使った場合、最初の1回目は時間を取られていますが、それ以降は直列処理のforループやstreamメソッドの場合よりも速く処理が行われていることが分かります。

【注意点1】対象となる要素が少ない場合は、並列処理の方が遅くなる

 しかし、並列処理にしたからといって全てのケースでパフォーマンス改善が行われるわけではありません。並列処理を行う場合、内部の処理ですべきことが多くなるため直列処理よりも大きなオーバーヘッドが発生することになります。

 対象となる要素が多い場合、そのオーバーヘッドのデメリットよりも並列で処理をすることによる高速化の方がメリットは大きくなりますが、要素が少ない場合、逆にオーバーヘッドのデメリットの方が大きくなってしまい、並列処理の方が遅くなってしまうこともあります。

 例えば、SalesDataのListを生成するのに日数のループを100万日から10日に減らした場合、結果は次のようになり、並列処理の方が遅くなっています(実行時間がかなり短くなるため、結果をナノ秒で算出するように変更しています)。

1回目 ------------------------------
forループ: time=408000ns
stream time=36956000ns
parallelStream time=7851000ns
2回目 ------------------------------
forループ: time=87000ns
stream time=165000ns
parallelStream time=193000ns
3回目 ------------------------------
forループ: time=78000ns
stream time=115000ns
parallelStream time=246000ns
4回目 ------------------------------
forループ: time=126000ns
stream time=173000ns
parallelStream time=216000ns
5回目 ------------------------------
forループ: time=125000ns
stream time=158000ns
parallelStream time=218000ns

 そのため、やみくもに並列処理にすることでパフォーマンスの改善が図れるわけではないので、注意してください。

【注意点2】コア数によってパフォーマンスは変わる

 また、コア数によって処理のパフォーマンスは変わってきます。4コアのCPUのマシンで良いパフォーマンス結果を得たからといって、2コアのCPUのマシンにそれを移植したとしても、同じように良いパフォーマンス結果を得られるというわけではありません。

【注意点3】元データの並び順は保障されない

 また、並列処理を使った際の注意事項として「元となるデータがListのように並び順が保障されている集合体でも、並列処理の際はその順番通り実行されるわけではない」点があります。

 例えば、次のようにforEachメソッドを使ってListの要素を標準出力する場合、直列処理だとListの順番通り出力されますが、並列処理だとListの順番通り出力されるわけではありません。

  1. List<String> list = Arrays.asList(
  2. "list1", "list2", "list3", "list4", "list5"
  3. );
  4. list.stream().forEach(e -> System.out.println("stream: " + e));
  5. list.parallelStream().forEach(e -> System.out.println("parallelStream: " + e));
stream: list1
stream: list2
stream: list3
stream: list4
stream: list5
parallelStream: list3
parallelStream: list5
parallelStream: list4
parallelStream: list2
parallelStream: list1
実行結果

次回はStream APIの主なメソッド

 今回はJava 8で追加されたjava.util.functionパッケージの汎用的な関数型インターフェースとJava 8の特に注目されているStream APIについて見てきました。

 このStream APIの概要が分かったところで、次回は実際Stream APIはどのように使われるのか主なメソッドについて見ていきます。そして、そこでラムダ式がどのように使われているのかを解説します。ご期待ください。

著者プロフィール

長谷川 智之(はせがわ ともゆき)

株式会社ビーブレイクシステムズ開発部所属。

社内サークル執筆チーム在籍。

主な執筆。

@IT連載『Javaの常識を変えるPlay framework入門

日経ソフトウェア連載『コツコツ学ぶAndroidネイティブアプリ開発教室』

前のページへ 1|2|3       

Copyright © ITmedia, Inc. All Rights Reserved.

スポンサーからのお知らせPR

Java Agile 鬮ォ�ェ陋滂ソス�ス�コ闕オ譁溷クキ�ケ譎「�ス�ウ驛「�ァ�ス�ュ驛「譎「�ス�ウ驛「�ァ�ス�ー

髫エ蟷「�ス�ャ髫エ魃会スス�・髫エ蟶キ�」�ッ闖ォ�」

注目のテーマ

4AI by @IT - AIを作り、動かし、守り、生かす
Microsoft & Windows最前線2025
AI for エンジニアリング
ローコード/ノーコード セントラル by @IT - ITエンジニアがビジネスの中心で活躍する組織へ
Cloud Native Central by @IT - スケーラブルな能力を組織に
システム開発ノウハウ 【発注ナビ】PR
あなたにおすすめの記事PR

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。