Parallel and Distributed Processing

Preview:

Citation preview

並列と分散

早稲田大学

丸山不二夫 @maruyama097

はじめに: 並列と分散

「並列処理(Parallel Processing)」と「並行処理(Concurrent Processing)」の違いは日本語では、必ずしも明確に意識されないことがある。英語でも、その含意には、共通の部分も多い。

一方、「並列処理」と「分散処理(Distributed Processing)」 は、外延的には、ほとんど同じであると言えるが、関心のフォーカスが異なっているように思う。

はじめに: Cloudと分散システム

こうしたフォーカスの違いは、次のような我々の感覚、Cloudのサービス提供のシステムを並列処理のシステムとしてよりはむしろ、分散システムとして捉えようとする志向と結びついている。

小論では、「並列から分散へ」という視点からネットワーク上のノードが協調動作をする「分散処理」と単一メモリー空間内の「並行処理」との対比で、システムの発展を考えて見る。

はじめに: multi-coreとInternet

こうした問題意識は、続々と登場しつつあるmulti-coreチップが、システム・デザインに与えるであろう影響を評価する上で重要であると考えている。

同時に、こうした問題意識は、誕生以来、その成長がIT技術に深い影響を与え続けている、我々が知りうる最大の分散システムとしてのInternetから、我々が何を学ぶべきかを考える、ヒントを与えてくれると考えている。

Leslie Lamport “My Writings” での単語の出現数

http://research.microsoft.com/en-us/um/people/lamport/pubs/pubs.html

Parallel 29個

Concurren* 116個

Distribut* 195個

Multi-core化の進行

続々と投入されるmulti-coreチップ

CPU名 コア数 製造会社

Nehalem-EX 8 Intel

Power 7 8 IBM

Magny-Cours 12 AMD

Rainbow Falls 16 Sun(Oracle)

Single-chip Cloud Computer

このネーミングは、Cloud上にある、スケーラブルなコンピュータのクラスターを、シリコン・チップ上に統合したものだからという。

一つのタイル(tile)につき二つのIAコアを持つ24個のタイルから構成される。48コア

セクション間双方向256GB/secの帯域を持つ、24個のrouter mesh network

4つの統合されたDDR3コントローラ。64GB

メッセージ・パッシングのハードウェア・サポート

Sequentialなプログラミング

並列・分散のない時代に戻ってみよう。

「人生は、善良でシンプルであった。」

Jim Waldo

TheValue クラス

Public class TheValue {

private int theValue;

public TheValue(int initialValue){

theValue = initialValue;

}

public void set(int newValue){

theValue = newValue;

}

public int get(){

return theValue;

}

}

TheValue クラスの逐次実行

TheValue theValue = new TheValue(0);

theValue.set(1);

theValue.get(); // 1 が返る

theValue.set(2);

thevalue.get(); // 2 が返る

theValue.set(3);

theValue.set(4);

theValue.set(5);

theValue.get(); // 5 が返る

Multi-Thread プログラミング (1) 排他制御

「ツールと優秀なプログラマだけが、Multi-Threadについて考えることが必要」

「我々は、順序を失う(複数のことが同時に起こる)。

これは、難しい。なぜなら、我々は、自然には、シーケンシャルに考えるから」

Jim Waldo

TheValueのMulti-Threadでの実行

theValue.set(2);

theValue.set(3);

theValue.set(7);

theValue.set(5);

theValue.set(8); theValue.set(6);

?

Syncronizedの利用

public syncronized void set(int newValue){ theValue = newValue; }

theValue.set(2);

theValue.set(3);

theValue.set(7);

theValue.set(5);

theValue.set(8);

theValue.set(6);

TheValue クラス

Public class TheValue {

private int theValue;

public TheValue(int initialValue){

theValue = initialValue;

}

public syncronized void set(int newValue){

theValue = newValue;

}

public int get(){

return theValue;

}

}

?

Javaのメモリーモデル

メモリー

キャッシュ

レジスター

Public class TheValue { private int theValue; …… …… }

あるスレッドが書き込んだ変数が、他のスレッドから正しく読めるか?

同じスレッドなら、正しく読める。

変数に対する読み書きが、同じLockでモニターされている場合。

syncronized

java.util.concurrent.lock.Lock

変数がvolatile宣言されている場合。

syncronizedとvolatile

syncronized

ロックを持っている一つのスレッドだけを実行

ブロックに入るときにキャッシュをクリア

ブロックを出るときにキャッシュをフラッシュ

volatile

変数を書き出す時にキャッシュをフラッシュ

変数を読み込む前にキャッシュをクリア

TheValue クラス

Public class TheValue {

private int theValue;

public TheValue(int initialValue){

theValue = initialValue;

}

public syncronized void set(int newValue){

theValue = newValue;

}

public syncronized int get(){

return theValue;

}

} setもgetも同時実行できない

TheValue クラス

Public class TheValue {

private volatile int theValue;

public TheValue(int initialValue){

theValue = initialValue;

}

public syncronized void set(int newValue){

theValue = newValue;

}

public int get(){

return theValue;

}

}

setは、同時実行できない getは、同時実行できる

Java.util.concurrent.atomic.*

Public class TheValue {

private AtomicInteger theValue;

public TheValue(int initialValue){

theValue = new AtomicInteger(initialValue);

}

public void set(int newValue){

do { int old = theValue.get(); }

while( ! theValue.compareAndSet(old, newValue)

}

public int get(){

return theValue.get();

}

}

Atomic命令 CAS(Compare and Set)の利用

/* Atomically increments by one the current value.

* @return the previous value

*/

public final int getAndIncrement() {

for (;;) {

int current = get();

int next = current + 1;

if (compareAndSet(current, next))

return current;

}

}

Atomic命令 CAS(Compare and Set)の利用

public final int addAndGet(int delta) {

for (;;) {

int current = get();

int next = current + delta;

if (compareAndSet(current, next))

return next;

}

}

java.util.concurrent.lock.* BoundedBuffer (続く)

class BoundedBuffer {

final Lock lock = new ReentrantLock();

final Condition notFull = lock.newCondition();

final Condition notEmpty = lock.newCondition();

final Object[] items = new Object[100];

int putptr, takeptr, count;

java.util.concurrent.lock.* BoundedBuffer (続く)

public void put(Object x) throws InterruptedException {

lock.lock();

try {

while (count == items.length) notFull.await();

items[putptr] = x;

if (++putptr == items.length) putptr = 0;

++count;

notEmpty.signal();

} finally {

lock.unlock();

}

}

java.util.concurrent.lock.* BoundedBuffer

public Object take() throws InterruptedException {

lock.lock();

try {

while (count == 0) notEmpty.await();

Object x = items[takeptr];

if (++takeptr == items.length) takeptr = 0;

--count;

notFull.signal();

return x;

} finally {

lock.unlock();

}

}

少し複雑な例 LinkedQueue

Item0 Item1 Item2

offer(item3)

Item0 Item1 Item2

Item3 poll()

Item1 Item2

Item0

LinkedQueueの Multi-Threadでの実行

lq.offer(1);

lq.poll();

lq.offer(7);

lq.offer(5);

lq.offer(3); lq.offer(6);

?

LinkedQueue lq = new LinkedQueue();

lq.poll()

java.util.concurrent.*

ConcurrentLinkedQueue<E> リンクノードに基づく、アンバウンド形式のスレッドセーフなキュー。

LinkedBlockingDeque<E> リンクノードに基づく、任意のバウンド形式のブロッキング両端キュー。

ArrayBlockingQueue<E> 配列に連動するバウンド形式のブロッキングキュー。

ConcurrentHashMap<K,V> 取得の完全な同時性および予想される調整可能な更新平行性をサポートするハッシュテーブル。

ConcurrentSkipListMap<K,V> スケーラブルな並行 ConcurrentNavigableMap。

……..

Multi-Thread プログラミング (2) 並列実行

Executor

ExecutorService

ThreadPoolExecutor

class SerialExecutor implements Executor { final Queue<Runnable> tasks = new ArrayDeque<Runnable>(); final Executor executor; Runnable active; SerialExecutor(Executor executor) { this.executor = executor; } public synchronized void execute(final Runnable r) { tasks.offer(new Runnable() { public void run() { try { r.run(); } finally { scheduleNext(); }

Executor

} }); if (active == null) { scheduleNext(); } } protected synchronized void scheduleNext() { if ((active = tasks.poll()) != null) { executor.execute(active); } } }

class NetworkService implements Runnable { private final ServerSocket serverSocket; private final ExecutorService pool; public NetworkService(int port, int poolSize) throws IOException { serverSocket = new ServerSocket(port); pool = Executors.newFixedThreadPool(poolSize); }

ExecutorService

public void run() { // run the service try { for (;;) { pool.execute(new Handler(serverSocket.accept())); } } catch (IOException ex) { pool.shutdown(); } } } class Handler implements Runnable { private final Socket socket; Handler(Socket socket) { this.socket = socket; } public void run() { // read and service request on socket } }

ForkJoin 処理の分割

JSR166y

Divide and Conquer

Result solve(Problem problem) {

if (problem が小さいものであれば)

直接、problemを解け;

else {

problemを独立の部分に分割せよ;

それぞれの部分を解く、subtaskをforkせよ;

全てのsubtaskをjoinせよ;

subresultからresultを構成せよ;

}

}

class MaxSolver extends RecursiveAction { private final MaxProblem problem; int result; protected void compute() { if (problem.size < THRESHOLD) result = problem.solveSequentially(); else { int m = problem.size / 2; MaxSolver left, right; left = new MaxSolver(problem.subproblem(0, m)); right = new MaxSolver(problem.subproblem(m, problem.size)); forkJoin(left, right); result = Math.max(left.result, right.result); } } } ForkJoinExecutor pool = new ForkJoinPool(nThreads); MaxSolver solver = new MaxSolver(problem); pool.invoke(solver);

Thresholdによる差異

Thresholdが大きいと、並列性がきかなくなる Thresholdが小さいと、並列化のためのオーバーヘッドが増える

Work-Stealing

それぞれのWorkerスレッドは、自分のスケジューリングQueueの中に、実行可能なTaskを管理している。

Queueは、double-link Queue(dequeu)として管理され、LIFOのpush,popとFIFOのtakeをサポートする。

あるWorkerのスレッドで実行されるtaskから生成されるsubtaskは、dequeにpushされる。

Workerスレッドは、自分のdequeを、LIFO(若い者が先)の順序で、taskをpopさせながら処理する。

Workerスレッドは、自分が実行すべきローカルなtaskがなくなった場合には、ランダムに選ばれた他のWorkerから、FIFO(古いものが先)のルールで、taskを取る(「盗む」)。

Work-Stealing

WorkerスレッドがJoin操作に会うと、それは、利用可能な別のtaskを、そのtaskが終了したという通知(isDone)を受け取るまで処理を続ける。

Workerスレッドに仕事がなく、どの他のスレッドからも仕事を取ることが出来なかったら、いったん元の状態に戻り、他のスレッドが、同様に全てアイドル状態だということが分かるまでは、そのあとも試行を続ける。

全てアイドルの状態の時には、トップレベルから、別のtaskが投入されるまで、Workerはブロックされる。

Work-Stealの動作

Pool.invoke()が呼ばれるとき、taskはランダムにdequeuに置かれる

Workerがtaskを実行しているとき

たいていは、二つのtaskをpushするだけ

そして、その一つをpopして実行する

そのうち、いくつかのWorkerが、top-levelのtaskを盗み始める

そうして、forkが終わると、taskは沢山のwork-queueに、自然に分散することになる

そうして、時間のかかるSequential部分を実行

ParallelArray データの分割

Extra JSR166y

Parallel Array

select-max をParallelArrayで実装するのは簡単。

ParallelArrayフレームワークは、array上の操作の分割を自動化する。

filtering, 要素のmapping, 複数のParallel Array上のcombination をサポートしている

全ての操作を、一つの並列実行に変換する

ParallelLongArray pa = ParallelLongArray.createUsingHandoff(array, fjPool); long max = pa.max();

Parallel Arrayで サポートされている基本操作

Filtering – 要素の部分を選択

複数のfilterを指定できる

ソートされたParallel Arrayには、Binary searchがサポートされている

Mapping – 選択された要素を、別の形式に変換

Replacement – 新しいParallelArrayを生成

Sorting, running accumulation

Aggregation – 全ての値を一つの値に

Max, min, sum, average

一般的な用途のreduce() メソッド

Application – 選択されたそれぞれの要素へのアクションの実行

ParallelArray students = new ParallelArray(fjPool, data); double bestGpa =

students.withFilter(isSenior).withMapping(selectGpa).max(); public class Student {

String name; int graduationYear; double gpa; } static final Ops.Predicate isSenior = new Ops.Predicate() {

public boolean op(Student s) { return s.graduationYear == Student.THIS_YEAR; } }; static final Ops.ObjectToDouble selectGpa = new Ops.ObjectToDouble() {

public double op(Student student) { return student.gpa; } };

Closureの利用

double bestGpa = students

.withFilter(

{Student s =>

(s.graduationYear == THIS_YEAR) }

) .withMapping(

{ Student s => s.gpa }

) .max();

.net PLINQ

LINQ / PLINQ

LINQ to Objects query:

PLINQ query:

int[] output = arr .Select(x => Foo(x)) .ToArray();

int[] output = arr.AsParallel() .Select(x => Foo(x)) .ToArray();

Sequence Mapping

Asynchronous Mapping

Async Ordered Mapping or Filter

IEnumerable<int> input = Enumerable.Range(1,100); bool[] output = input.AsParallel() .Select(x => IsPrime(x)) .ToArray();

var q = input.AsParallel() .Select(x => IsPrime(x)); foreach(var x in q) { ... }

var q = input.AsParallel().AsOrdered() .Select(x => IsPrime(x)); foreach(var x in q) { ... }

Search

More complex query

int result =

input.AsParallel().AsOrdered()

.Where(x => IsPrime(x))

.First();

int[] output = input.AsParallel() .Where(x => IsPrime(x))

.GroupBy(x => x % 5)

.Select(g => ProcessGroup(g)) .ToArray();

Software Managed Coherence

Multi-Core SCCでの整合性の処理

Multi-Coreが今後取り組むべき課題

We believe software managed coherency on non-coherent many-core is the future trend

A prototyped partially shared virtual memory system demonstrates it can be:

Easy to program

Comparable performance vs. hardware coherence

Adaptive to future advanced usage models

Also opens new research opportunities

Challenges for future research

This revived “software managed coherency” topic opens many “cold cases”

What are the right software optimizations?

Prefetching, locality, affinity, consistency model

And more…

What is the right hardware support?

How do emerging workloads adapt to this?

複数のマシンでの分散処理

「マルチ・マシン (MM) は、同一ネットワーク上の マルチ・プロセスと同じではないのだが、ある人たちは、同じだと考えている」

「我々は、状態を失う。「システム」のグローバルな状態というのは、虚構である。興味深い分散システムには、整合的な状態というものは存在しない。」

Jim Waldo

“A Note on Distributed Computing”

http://www.sunlabs.com/techrep/1994/smli_tr-94-29.pdf

ローカルなプログラミングとリモートなプログラミングを、はっきりと区別すべきだという立場

Jim Waldo et al.

ネットワーク上のシステムで相互作用するオブジェクトは、単一のアドレス空間で相互作用するオブジェクトとは、本来的に異なったやり方で取り扱われるべきであると、我々は主張している。

こうした違いが要求されるのは、ネットワーク上のシステムでは、プログラマは遅延の問題を意識せねばならず、異なったメモリーアクセスのモデルを持ち、並列性と部分的失敗(partial failure)の問題を考慮にいれなければならないからである。

我々は、ローカルとリモートのオブジェクトの違いを覆い隠そうと試みる、沢山のネットワーク・システムを見てきた。そして、これらのシステムは、頑健さと信頼性という基本的な要請を満たすことに失敗していることを示そうと思う。

こうした失敗は、過去においては、構築されたネットワーク・システムの規模の小ささで、隠蔽されていた。しかしながら、近未来に予想される、企業規模のネットワークシステムにおいては、こうした隠蔽は不可能となるであろう。

Leaseを利用した分散Transaction

CloudやBASE概念登場以前で、筆者が知る限り、もっともスマートな分散Transactionのシステムは、分散システムのPartial Failureの問題に対応するためにLease概念を応用した、Jiniのそれであった。

「Jini セミナー 1999」 第10章 Transaction http://maruyama.cloud-market.com/summer99/summer99.pdf

Java Spaceを利用した 分散システムの排他制御

同様に、分散システムの排他制御の、もっともエレガントな例は、Java Spaceを利用したものであったと思う。

「Rio / JavaSpace セミナー」 同期とリソースの共有 http://maruyama.cloud-market.com/summer02/rio.pdf

データあるいは処理の分割

Googleの登場は、複数のマシンが協調動作する分散処理技術に、大きな飛躍をもたらした。

ここでは、彼らの中心技術であるMapReduceを対象に、その新しさを考えて見る。

maruyama

maruyama

maruyama

maruyama

maruyama

maruyama

maruyama

maruyama

maruyama

maruyama

searchALL searchA

searchB

searchC

maruyama

maruyama

maruyama

maruyama

maruyama

maruyama

maruyama

maruyama

maruyama

maruyama

searchALL searchW

searchX

searchY

searchZ

検索結果は、 データの分割の仕方に依存しない

searchALL=searchA+searchB+searchC =searchW+searchX+searchY+searchZ

maruyama

maruyama

maruyama

maruyama

maruyama

searchW

searchX

searchY

searchZ

maruyama

maruyama

maruyama

maruyama

maruyama

searchA

searchB

searchC

結合律と可換律

結合律 searchALL=searchA+searchB+searchC =searchA+(searchB+searchC) =(searchA+searchB)+searchC

可換律 searchALL=searchA+searchB+searchC =searchA+(searchB+searchC) =searchA+(searchC+searchB) =(searchC+searchB)+searchA

MapReduce

MapReduceアルゴリズム適用可能例 LogからURLのアクセス頻度を累計する

mapは、Logファイルを処理して各URL毎に(URL,1)を中間出力する。

mapreduceライブラリは、中間出力をURL毎にまとめる。(中間出力のsort)

reduceは、同じURLをカウントアップして、(URL, total count)として出力する。

URL_W

URL_A

URL_K

URL_K

URL_A

URL_N

URL_A

URL_I

URL_H

URL_O

URL_W,1

URL_A,1

URL_K,1

URL_K.1

URL_A,1

URL_N,1

URL_A,1

URL_I,1

URL_H,1

URL_O,1

URL_A,1

URL_A,1

URL_A,1

URL_H.1

URL_I,1

URL_K,1

URL_K,1

URL_N,1

URL_O,1

URL_W,1

URL_A,3

URL_H,1

URL_I,1

URL_K,2

URL_N,1

URL_O,1

URL_W,1

map sort reduce

先の処理の分散化を考える

map部分の処理は、複数のマシン上で分割可能である。

同様に、reduce部分の処理も、URL_AやURL_Kのようなデータの繰り返しの境界を破らなければ、容易に分割可能である。

問題は、mapの出力を分割してsortして、reduceに渡す方法である。

このとき、mapの出力を、同じキーは同じグループに属するように分割すればいい。

GroupByKeyが本質的

URL_W,1

URL_A,1

URL_K,1

URL_K.1

URL_A,1

URL_N,1

URL_A,1

URL_I,1

URL_H,1

URL_O,1

URL_A,3

URL_H,1

sort reduce

URL_A,1

URL_A,1

URL_A,1

URL_H.1

URL_K,1

URL_K,1

URL_I,1

URL_W,1

URL_N,1

URL_O,1

URL_A,1

URL_A,1

URL_A,1

URL_H.1

URL_I,1

URL_K,1

URL_K,1

URL_N,1

URL_O,1

URL_W,1

partition

URL_N,1

URL_O,1

URL_W,1

URL_I,1

URL_K,2

分散処理可能性

分散処理には、いくつかのタイプが存在する。従来の並列処理では、必ずしも明確に意識されていたとは限らない、分散処理に特有の、しかしながら、実践的には極めて有用な分散処理のある型を考えて見よう。ここでも、分析のモデルはMapReduceである。

分散処理可能性 1

あるデータの集合A={Ai}を、n個のマシン上の同一のプログラムPで処理して、あるデータの集合B={Bj}を生成するとしよう。

SPMD(Single Process Multi Data)

MPI = SPMD + message Passing

分散処理可能性 2

分散処理可能性1に次の条件を加えたもの

処理の間、ノード間でデータに関する情報の交換はないものとする。

Dryad = メッセージが構成するグラフ全体を汎用的に処理しようという試み

(おそらく、一般化のし過ぎ)

分散処理可能性 3

分散処理可能性2に次の条件を加えたもの

任意のn個のノードが、同一のA={Ai} に対して同一の結果B={Bj}を生み出す

Scalability

ノードを増やしても減らしても、処理の結果は変わらない

強い意味での分散処理可能性

あるデータの集合A={Ai}を、n個のマシン上の同一のプログラムPで処理して、あるデータの集合B={Bj}を生成するとしよう。

かつ、処理の間、ノード間でデータに関する情報の交換はないとしよう。

この時、 任意のnに対して、同一のA={Ai} に対して同一の結果B={Bj}を生み出すようなプログラムPが存在するならば、<A,B,P>は、強い意味で分散処理可能であるという。

Master Worker Pattern

Master

Worker

Worker

Worker

Worker

Worker

Worker

それぞれのWorkerは、

タスクを一つ取り出して処理せよ!

終わったら、作業を繰りかえせ!

強い意味での 分散処理可能性の例

Master Worker Pattern

てすきのWorkerは、どんどん働け! 働け! 働け! タスクがなくなるまで働け!

強い意味での 分散処理可能性の例

Master Worker Pattern

作業終了! 休んでよし。

強い意味での 分散処理可能性の例

Master/Workerパターンは、 強い意味での分散処理可能性を満たす

同一のプログラム: Master/Workerパターンでは、各Workerは、同じ仕事を行う。

情報の交換はない: Master/Workerパターンでは、各Workerは他のWorkerの仕事に関心を持たない。

任意のnに対して同一の結果: Master/Workerパターンでは、 処理の結果は、Workerの数によらない。

分散処理可能性から MapReduceを再解釈する

Master/Worker パターンでの Map処理

同じキーを持つ データを、同じ ノードに集めて 整列する Shuffling処理

同じキーを持つ データを、整理する Reduce処理 ここでも、Master/ Workerパターンが 利用される。

・・・・・・・

・・・・・・・

・・・・・・・

・・・・・・・

・・・・・・・

MAP SHUFFLE REDUCE

分散処理のいくつかの問題

並列と分散

分散処理での一群の処理を、単一ノード(multi-coreを含む)内で行うことが出来る並行・並列処理と、複数のノードをまたいで行う分散処理に分けることが出来る。

もちろん、こうした区別は、相対的なものである。同一ノード内の並列処理を、複数のノードに分散して行うことはできるし、複数のノードをまたいで行う処理も、単一ノード内の並列処理として実現しうるからである。

データの存在様式

並列と分散の両者が分離するのは、主要に問題の「大きさ」による。

問題の「大きさ」は、処理の単純さ複雑さによっても決まるし、対象とするデータの大きさによっても決まる。

データの大きさでは、単一のノードによっては収まりきれず、複数のノードによって保持されるデータが存在する。それは、そうした存在の様式にも拘らず、単一のデータであることに注意しよう。

処理の分割とデータの分割

分散処理の手法を、処理の分割とデータの分割に分けられるとは限らない。分散された処理には分散されたデータが付随し、分散されたデータには、分散された処理が付随するからである。

分散処理の管理主体

分散処理は、物理的には各ノード上で分散して行われるが、論理的には、その処理の目的は明確である。

では、その分散したノードの協調動作は、誰によって管理されているのであろうか?

一つの答えは、管理主体の存在しない、自律的な分散処理である。

ただ、それで十分に効率的であろうか?

To Be Continued.

Recommended