57
Copyright 2015 FUJITSU LIMITED [3-1] Java のののののののののののののののののののののののののののの のの のの の 201548 ののののののの のの のの Java Day Tokyo 2015

JavaDayTokyo2015 [3-1]

Embed Size (px)

Citation preview

Copyright 2015 FUJITSU LIMITED

[3-1]

Java の関数型言語への挑戦/ベターオブジェクト指向言語からの脱皮

2015年4月8日富士通株式会社数村 憲治

Java Day Tokyo 2015

2 Copyright 2015 FUJITSU LIMITED

関数型言語とは Java と関数型言語 関数型言語の特徴

参照透過性再帰ストリーム遅延評価

ストリームで注意すべきこと Java における並列プログラミングの実力

アジェンダ

3

関数型言語 (FP) とは

Copyright 2015 FUJITSU LIMITED

Haskell Scala

Erlang

F#

Lisp

無名関数

遅延評価

末尾再帰

高階関数

参照透過性

モナド

4

なぜ、今、関数型言語か

Copyright 2015 FUJITSU LIMITED

ハードウェアメニーコア

大容量メモリSSD

並列プログラミングの必然性

関数型言語に注目

手続き型言語では難しいIoTビッグデータ

インメモリデータベース

環境・プラットフォーム

5

メニーコアで性能向上させるには

Copyright 2015 FUJITSU LIMITED

プログラマーは、どれだけ頑張ればよいのか?

並列処理は、どんな言語でも頑張ればできる。

低い高い熟練プログラマ手続き型言語

熟練プログラマ関数型言語

一般プログラマ関数型言語

一般プログラマ手続き型言語

性能+品質

6

並列と並行

Copyright 2015 FUJITSU LIMITED

並列 (Parallel) とは、一つの作業を複数に分割し、それぞれを「本当に」同時に実行することで、全体処理時間を短くする。

並行 (Concurrent) とは、複数の作業を別々のスレッドで、( 同期を取りながら ) 実行する。必ずしも「本当に」同時に実行しなくてもよい。

7

並列と並行の例

Copyright 2015 FUJITSU LIMITED

Parallel GC

Concurrent GC

GC thread #1

GC thread #2

GC thread #3

GC 処理を複数スレッドで同時に実行

GC thread

APP thread #1

APP thread #2

GC 処理とアプリ処理を同時に実行

8

並行・並列プログラミングレベル

Copyright 2015 FUJITSU LIMITED

レベル1 レベル2 レベル3

スレッド生成・終了

アプリ 言語システム 言語システム

排他制御 アプリ 言語システム 言語システム

スレッドプール管理

アプリ 言語システム 言語システム

タスク指向fork/join ・future

なし アプリ 言語システム

Actor モデル なし なし アプリ

ストリーミング なし なし アプリC/C++Java SE 5-7

FP

Java SE 1.4 Java SE 8

9

レベル1からレベル2へ

Copyright 2015 FUJITSU LIMITED

スレッド指向・スレッドを作るところから始める・スレッドから子スレッドを作ると収集つかない・スレッドプールは自作

タスク指向・スレッドの生成やプーリングはシステムで・プログラマは、スレッドで行う仕事にフォーカス・結果を同期、非同期いずれでも確認可能

10

スレッド指向とタスク指向

Copyright 2015 FUJITSU LIMITED

スレッド指向 タスク指向class Target implements Runnable{ public void run() { // do something }}Target target = new Target();Thread t1 = new Thread(target);t1.start();t1.join();result = target.get();

class Task extends RecursiveTask <Object>{ Object compute() { // do something }}ForkJoinPool pool = new ForkJoinPool(N);Future future = pool.submit(new Task());if (future.isDone()) result = future.get();

11

レベル2からレベル3へ

Copyright 2015 FUJITSU LIMITED

アクターモデルakka 等Java の標準ライブラリには含まれていない

レベル2 レベル3ストリーム API

並列処理

並行処理

12 Copyright 2015 FUJITSU LIMITED

関数型言語とは Java と関数型言語 関数型言語の特徴

参照透過性再帰ストリーム遅延評価

ストリームで注意すべきこと Java における並列プログラミングの実力

アジェンダ

13

パラダイムシフト

Copyright 2015 FUJITSU LIMITED

命令型 オブジェクト指向 関数型

C C++ HaskellScalaJava

ベター Cベター OOP

??

?

継承カプセル化・・・

参照透過ラムダ・・・

モジュール構造化・・・

14

Java はどこへ向かうのか

Copyright 2015 FUJITSU LIMITED

It is my belief that the best direction for evolving Javais to encourage a more functional style of programming. The role of Lambda is primarily to support the development and consumption of more functional-like libraries; I've offered examples such as filter-map-reduce to illustrate this direction.

Simply put, we believe the best thing we can do for Java developers is to give them a gentle push towards a more functional style of programming. We're not going to turn Java into Haskell, nor even into Scala.

http://mail.openjdk.java.net/pipermail/lambda-dev/2011-August/003877.html

Brian Goetz

15

OOP と FP

Copyright 2015 FUJITSU LIMITED

・データはオブジェクト。メッセージはメソッド。・オブジェクトにメッセージを送り状態を変化。

OOP

FP・データは不変。・データに関数を適用し、新しいデータを作る。

データ ( オブジェクト )1 → 3 → 9

データ :1 データ :3 データ :9

メッセージ: +2

メッセージ: X3

関数: +2 関数: X3

16

FP 品質 - 制約プログラミング

Copyright 2015 FUJITSU LIMITED

関数型言語で新たに出来ることよりも、オブジェクト指向言語で出来たことが、関数型言語では出来なくなることが重要。

破壊的代入 副作用

プログラマに制約をかけ、自由度を減らす

ループ

プログラム品質の向上

17

FP 品質 - 数学的プログラミング

Copyright 2015 FUJITSU LIMITED

証明された定理を積み上げて新たな定理を証明動作保証された小さな関数を合成して新たな関数を作成

コンパイルが通れば動作の証明 ( 型に関して )

数学

Curry-Howard 同型対応

プログラム

命題=型  証明=プログラム直感主義命題論理と単純型付ラムダ計算の対応

18 Copyright 2015 FUJITSU LIMITED

関数型言語とは Java と関数型言語 関数型言語の特徴

参照透過性再帰ストリーム遅延評価

ストリームで注意すべきこと Java における並列プログラミングの実力

アジェンダ

19

参照透過性

Copyright 2015 FUJITSU LIMITED

Java のメソッド

関数型言語の関数・関数の引数が同じなら、戻り値は同じ・関数呼び出しによる副作用 ( 状態変化 ) なし

→ 並列化しやすい

・メソッド呼び出しは、メッセージ送信・メソッドはオブジェクトの状態を変化させる→ 並列化しにくい

・変数の再代入 ( 破壊的代入 ) ができない

20

Java で参照透過を実現するには

Copyright 2015 FUJITSU LIMITED

・メソッドは副作用がないように書く。

・変数には、「 final 」をつける。

・ Immutable オブジェクトを作成・使用。

→ できる範囲で

21

Immutable オブジェクト

Copyright 2015 FUJITSU LIMITED

char* str = ・・・ str[3] = ‘\0’;

String str = ・・・str = str.substring(3);

C プログラム

Java プログラム

オーバランの危険性

String は immutable常に新しい String 生成

複数スレッドで操作するとデータ破壊の可能性

Immutable な String が Java を高品質に

22

Mutable なクラス

Copyright 2015 FUJITSU LIMITED

public class SuperMarket { private ArrayList<Food> foodList; public List<Food> get() { return foodList; } public void add(Food food) { foodList.add(food); } public void remove(Food food) { foodList.remove(food); }

複数スレッドから呼べない

mutable

安全にするには?(並行プログラミング)

23

mutable なクラスを安全に

Copyright 2015 FUJITSU LIMITED

public class SuperMarket { private ArrayList<Food> foodList; public synchronized List<Food> get() { return foodList; } public synchronized void add(Food food) { foodList.add(food); } public synchronized void remove(Food food) { foodList.remove(food); }

get メソッドで返される List オブジェクトがmutable

24

mutable なクラスを安全に

Copyright 2015 FUJITSU LIMITED

public class SuperMarket { private ArrayList<Food> foodList; public synchronized List<Food> get() { return foodList.clone(); } public synchronized void add(Food food) { foodList.add(food); } public synchronized void remove(Food food) { foodList.remove(food); }

25

更新があまりないなら

Copyright 2015 FUJITSU LIMITED

public class SuperMarket { private CopyOnWriteArrayList<Food> foodList; public synchronized List<Food> get() { return foodList.clone(); } public synchronized void add(Food food) { foodList.add(food); } public synchronized void remove(Food food) { foodList.remove(food); }

java.util.concurrent.CopyOnWriteArrayList の使用でsynchronized を削減

26

List を更新されたくないなら

Copyright 2015 FUJITSU LIMITED

public class SuperMarket { private CopyOnWriteArrayList<Food> foodList; public List<Food> get() { return Collections.unmodifiableList( foodList.clone()); }

java.util.Collections.unmodifiableList でread-only の List の作成

27 Copyright 2015 FUJITSU LIMITED

関数型言語とは Java と関数型言語 関数型言語の特徴

参照透過性再帰ストリーム遅延評価

ストリームで注意すべきこと Java における並列プログラミングの実力

アジェンダ

28

ループから再帰

Copyright 2015 FUJITSU LIMITED

int foo(Bar[] bars) { int result = 0; for (int i = 0 ; i < bars.length ; ++i) result += bars[i].get(); return result;}

int foo(final Bar[] bars) { return foo1(0, 0, bars); }int foo1(final int n, final int result, final Bar[] bars) { if (n == bars.length) return result; return foo1(n+1, bars[n].get()+result, bars);} StackOverflowErro

r

ループ

再帰

再代入の解消

29

末尾再帰最適化 (TCO)

Copyright 2015 FUJITSU LIMITED

Java(JIT) では未対応

最後の処理が自分自身への呼び出しなら、関数コール ( 新しいスタック作成 ) しない。ループ処理のように同じスタックで処理する。

int foo1(final int n, final int result, final Bar[] bars) { if (n == bars.length) return result; return foo1(n+1, bars[n].get()+result, bars);}

最後の処理が自分自身への呼び出し

30 Copyright 2015 FUJITSU LIMITED

関数型言語とは Java と関数型言語 関数型言語の特徴

参照透過性再帰ストリーム遅延評価

ストリームで注意すべきこと Java における並列プログラミングの実力

アジェンダ

31

ストリーム ( パイプライン )とは

Copyright 2015 FUJITSU LIMITED

UNIX のパイプ処理% cat input | grep keyword | sort | head -5 > output

read(input).stream() .filter(keyword) .sorted() .limit(5) .writeTo(output)

ストリームプログラミング

FP スタイルinput func1 func2 func3 output

32

ループからストリーム

Copyright 2015 FUJITSU LIMITED

Java SE 7 の糖衣構文

int foo(Bar[] bars) { int result = 0; for (int i = 0 ; i < bars.length ; ++i) result += bars[i].get();

int foo(Bar[] bars) { int result = 0; for (Bar b : bars) result += b.get();

int foo(Bar[] bars) { return Arrays.stream(bars) .mapToInt( b -> b.get() ); .sum();

Java SE 8のstream API

33

ループ v.s. ストリーム ( 並列化 )

Copyright 2015 FUJITSU LIMITED

int foo(Bar[] bars) throws InterruptedException { BarSum[] barSum = new BarSum[N]; for (int i = 0 ; i < N ; ++i) barSum[i] = new BarSum(bars, bars.length/N*i, bars.length/N*(i+1)); int result = 0; for (int i = 0 ; i < N ; ++i) { barSum[i].join(); result += barSum[i].result; } return result;}class BarSum extends Thread { Bar[] bars; int begin, end; int result; BarSum(Bar[] bars, int begin, int end) { this.bars = bars; this.begin = begin; this.end = end; } public void run() { for (int i = begin ; i < end ; ++i) result += bars[i].get(); }}

ループの並列化int foo(Bar[] bars) { return Arrays.stream(bars) .parallel() .mapToInt( b -> b.get() ); .sum();

ストリームの並列化

34 Copyright 2015 FUJITSU LIMITED

関数型言語とは Java と関数型言語 関数型言語の特徴

参照透過性再帰ストリーム遅延評価

ストリームで注意すべきこと Java における並列プログラミングの実力

アジェンダ

35

遅延評価

Copyright 2015 FUJITSU LIMITED

デメリット・メモリ使用量が増える・デバッグが難しくなる

必要になるまで評価しない

メリット・無駄な処理をしなくてよくなる・重い処理を後回しにできる・並列処理効率を上げられる

36

Java での評価タイミング

Copyright 2015 FUJITSU LIMITED

n の値はここで決定

if ( f1() && f2() ) {f1() が false なら f2() は評価しない

int n = foo();

// n に関係ない処理

System.out.println(n);

一般的には遅延評価と言わない

例1

例2

ここなら遅延評価というJava では

普通のメソッド呼び出しは遅延評価しない

37

Java における遅延評価

Copyright 2015 FUJITSU LIMITED

ストリームの中間操作は遅延評価終端操作実行時に評価

int foo(List<Integer> list) { return list.stream() .filter(i -> i > 30) .mapToInt(i -> i * 2) .limit(50) .sum();}

中間操作中間操作

終端操作中間操作

38

ストリーム操作の種類

Copyright 2015 FUJITSU LIMITED

ストリーム操作

ステートフル操作distinct/sorted

ステートレス操作map/filter

終端操作forEach/count

中間操作

遅延評価

並列化しやすい

39

多段ループ処理

Copyright 2015 FUJITSU LIMITED

int foo(List<Integer> list) { ArrayList<Integer> list2 = new ArrayList<>; for (int i : list) if (i > 30) list2.add(i);

ArrayList<Integer> list3 = new ArrayList<>; for (int i : list2) list3.add(i * 2)

int result = 0; int count = 0; for (int i : list3) { result += i; count ++; if (count == 50) break; } return result;

待ち合わせ

待ち合わせ

40

メニーコアにおける並列化

Copyright 2015 FUJITSU LIMITED

コア1理想

現実 コア2

コア2

コアn

コアn

コア1

タスクタスクタスク

タスク タスクタスク

タスク タスク

タスクタスクタスク

タスクタスクタスク

・・・

同時に終わる

タスク

タスクタスク

タスク空き 空き

・・・

CPU に空き

41

ループの並列処理化

Copyright 2015 FUJITSU LIMITED

int foo(List<Integer> list) { int result = 0; int count = 0; for (int i : list) { if (i > 30) { result += i *2; count ++; if (count == 50) break; } } return result;}

複数スレッドで同期を取りながら50数える必要がある

ループは一つになったが

並列化するには、

42

遅延評価と並列化

Copyright 2015 FUJITSU LIMITED

int foo(List<Integer> list) { return list.stream() .parallel() .filter(i -> i > 30) .mapToInt(i -> i * 2) .limit(50) .sum();}

待ち合わせ待ち合わせ待ち合わせ

遅延評価がないと

並列化の源は遅延評価

43 Copyright 2015 FUJITSU LIMITED

関数型言語とは Java と関数型言語 関数型言語の特徴

参照透過性再帰ストリーム遅延評価

ストリームで注意すべきこと Java における並列プログラミングの実力

アジェンダ

44

並列から逐次へ

Copyright 2015 FUJITSU LIMITED

ソート後は逐次に

someStream .parallel() .filter(e -> e.shouldHandle()) .sorted() .limit(100) .forEach(e -> System.out.println(e));

someStream .parallel() .filter(e -> e.shouldHandle()) .sorted() .sequential() .limit(100) .forEach(e -> System.out.println(e));

ソートされない出力

45

stream は速やかに流す

Copyright 2015 FUJITSU LIMITED

Synchronized を使わない

static Object lock = new Object();Bar foo(List<Bar> list) { return list.stream() .parallel() .map(b -> { synchronized (lock) { return b.baz();} }) .max(comparator);}

46

副作用を書かない

Copyright 2015 FUJITSU LIMITED

void foo(List<Bar> list) { ArrayList<Bar> result = new ArrayList<>(); list.stream() .parallel() .filter(b -> b.baz()) .forEach(b -> result.add(b));

List<Bar> result = list.stream() .parallel() .filter(b -> b.baz()) .collect(Collectors.toList());

47

Collectors( リダクション )

Copyright 2015 FUJITSU LIMITED

・リダクション用の static メソッド群が定義・ Stream.collect() メソッドに指定する・ toList/groupingBy/summingInt など

例:

java.util.stream.Collectors

List<Bar> result = list.stream() .parallel() .filter(b -> b.baz()) .collect(Collectors.toList());

48

Collector と Stream の属性

Copyright 2015 FUJITSU LIMITED

UNORDERED: 順序が保証されない

IDENTITY_FINISH: フィニッシャー不要CONCURRENT: アキュムレータの同時呼び出し可

Collector の属性

Stream の属性unordered: 順序不定のストリームparallel: 並列ストリーム

49

並列リダクションの条件

Copyright 2015 FUJITSU LIMITED

(A) Stream が parallel

かつ

(B) Collector が CONCURRENT

かつ

(C) Stream が unordered または Collector が UNORDERED

50

並列リダクション

Copyright 2015 FUJITSU LIMITED

Map<Foo, Bar> result = source.stream() .parallel() .filter(b -> b.baz()) .collect(toMap(b->b.foo(), b));

ConcurrentMap<Foo, Bar> result = source.stream() .parallel() .filter(b -> b.baz()) .collect(toConcurrentMap(b->b.foo(), b));

非並列

並列

CONCURRENT属性なし

CONCURRENT属性UNORDERED属性

parallel stream

parallel stream

51 Copyright 2015 FUJITSU LIMITED

関数型言語とは Java と関数型言語 関数型言語の特徴

参照透過性再帰ストリーム遅延評価

ストリームで注意すべきこと Java における並列プログラミングの実力

アジェンダ

52

Stream API による並列性能

Copyright 2015 FUJITSU LIMITED

レベル2のプログラム (fork/join使用 ) とレベル3のプログラム (stream使用 ) との比較

ベンチマークモデル100万件の購買伝票を基に、購入関連の高い商品群を抽出し、未購入ユーザにレコメンドする。

指標生産性 ( ソース行数 ) と性能 ( 実行時間 )

53

生産性 ( ソース行数 )

Copyright 2015 FUJITSU LIMITED

static Map<Account, List<Product>> createProductMap(Account[] accounts) { return Arrays.stream(accounts).parallel() .collect(Collectors.toMap(Function.identity(), acc -> acc.records.stream() .flatMap(rec -> rec.orders.stream()) .map(ord -> ord.product) .distinct() .collect(Collectors.toList())));}

static Map<Account, List<Product>> createProductMap(Account[] accounts) { Map<Account, List<Product>> map = new ConcurrentHashMap<>(); ArrayList<ForkJoinTask> tlist = new ArrayList<>(); for (Account acc : accounts) { ForkJoinTask task = pool.submit(new Runnable() {

public void run() { ArrayList<Product> list = new ArrayList<>(); for (PurchaseRecord pr : acc.records) for (Order ord : pr.orders)

if (!list.contains(ord.product)) list.add(ord.product);

map.put(acc, list); }});

tlist.add(task); } for (ForkJoinTask task : tlist) task.join(); return map;}

レベル2のソース レベル3のソースレベル2 (fork/join) レベル3( stream)

0

20

40

60

80

100

120

140ソース行数(除共通部分)

ステップ数

54

性能 ( 処理時間 )

Copyright 2015 FUJITSU LIMITED

M10-4S SPARC64-X 4CPU x16core x2thread

・スレッド数が少ないほど、レベル2の方が良い・スレッド数が大きくなると、差はほとんどなくなる傾向

2 4 8 16 32 64 1280

5000

10000

15000

20000

25000

30000

35000

40000

レベル2(fork/join)

スレッド数

時間(ms)

55

まとめ

Copyright 2015 FUJITSU LIMITED

オブジェクト指向言語

Java による高品質並列処理

関数型言語の仕組みを知る

メニーコアを使い切る並列プログラミング

ストリームプログラミング

参照透過・遅延評価の活用

56

Q&A

Copyright 2015 FUJITSU LIMITED

Copyright 2010 FUJITSU LIMITED