parallel and distributed processing
TRANSCRIPT
並列と分散
早稲田大学
丸山不二夫 @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)
Intel SCC (Single-chip Cloud Computer)
Intel Labが2010年3月30日に発表。
http://techresearch.intel.com/articles/Tera-Scale/1826.htm
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.