Mercurial > hg > Papers > 2014 > toma-master
comparison paper/chapter1.tex @ 39:a7981a22f12e
describe the eval monad
author | Daichi TOMA <toma@cr.ie.u-ryukyu.ac.jp> |
---|---|
date | Tue, 04 Feb 2014 02:26:58 +0900 |
parents | 8d934599d0c5 |
children | ff15fb78a3ae |
comparison
equal
deleted
inserted
replaced
38:8d934599d0c5 | 39:a7981a22f12e |
---|---|
274 $ ./par +RTS -N2 | 274 $ ./par +RTS -N2 |
275 \end{lstlisting} | 275 \end{lstlisting} |
276 | 276 |
277 当然これだけでは並列に動かず、並列に実行できるようにプログラムを書く必要がある。 | 277 当然これだけでは並列に動かず、並列に実行できるようにプログラムを書く必要がある。 |
278 | 278 |
279 Haskell は遅延評価を行うため、必要となるまで式の評価が遅延される。 | 279 Control.Parallel.Strategies モジュールにある、 |
280 普段は気にする必要はないが、並列実行ではどのように評価されるか意識する必要がある。 | 280 Eval モナドを用いた並列化について説明する。 |
281 並列に動くように処理を分割したとしても、値が必要になるまで評価されない。 | 281 Eval モナドは並列処理を行うためのモナドである。 |
282 この問題は、Control.DeepSeq モジュールにある deepseq 関数を使用し、式を即時評価することで解決できる。 | 282 |
283 | 283 Eval モナドで並列処理を行う使用例を示す。 |
284 Haskellの並列化手法はいくつかあるが、その中で最もシンプルなのは Eval モナドを利用することである。 | 284 |
285 Control.Parallel.Strategies モジュールをインポートすることで使用できる。 | 285 %% 完全に動くプログラム |
286 | 286 \begin{lstlisting}[caption=Evalモナドの使用例] |
287 import Control.Parallel.Strategies | |
288 | |
289 main = print (runEval test) | |
290 | |
291 num :: Integer | |
292 num = 1000000 | |
293 | |
294 test :: Eval (Integer, Integer) | |
295 test = do | |
296 a <- rpar (sum' 0 num) | |
297 b <- rpar (sum' num (num*2)) | |
298 return (a,b) | |
299 | |
300 | |
301 sum' :: Integer -> Integer -> Integer | |
302 sum' begin end = if begin < end | |
303 then begin + (sum' (begin + 1) end) | |
304 else begin | |
305 \end{lstlisting} | |
306 | |
307 まず、Eval モナドが定義された、Control.Parallel.Strategies をロードし、Eval モナドを利用できるようにしている。 | |
308 | |
309 Haskell のプログラムはmainという名前と実行したい関数を関連付けることで実行される。 | |
310 今回は、print (runEval test)が実行される。 | |
287 | 311 |
288 \begin{lstlisting}[label=eval, caption=Eval モナド] | 312 \begin{lstlisting}[label=eval, caption=Eval モナド] |
289 data Eval a | 313 data Eval a |
290 instance Monad Eval | 314 instance Monad Eval |
291 | 315 |
292 runEval :: Eval a -> a | 316 runEval :: Eval a -> a |
293 | |
294 rpar :: a -> Eval a | 317 rpar :: a -> Eval a |
295 rseq :: a -> Eval a | 318 \end{lstlisting} |
296 \end{lstlisting} | 319 |
297 | 320 並列処理を行うには、rpar を使う。 |
298 rpar は、引数が並列処理可能であることを示す。 | 321 rpar で挟んだ関数は並列に実行される。 |
299 rseq は、引数を評価し、結果を待つように示す。 | 322 Eval モナドの関数の型をみると、rpar は、a を モナドに包み、逆にrunEval はモナドから a を取り出している。 |
300 どちらも式が完全に即時評価されるわけではないので、出力を挟んだり、deepseq 関数をうまく活用する必要がある。 | 323 rpar で並列化可能計算を示したあと、runEvalで実行する。 |
301 runEval は、評価を実行し結果を返す操作である。 | 324 |
302 実際の利用方法として2つのパターンを紹介する。 | 325 test の = のすぐ後にあるdoはモナドのためのシンタックスシュガーであり、do 構文と呼ばれる。 |
303 | 326 Haskell では、モナドを単一のモナドとするために、do構文を使う。 |
304 | 327 $>>$= (bind)を使ってまとめることもできるが、do 構文を使うことでbindの入れ子構造を手続き言語のような書き方で書くことができる。 |
305 \begin{lstlisting}[label=rpar/rpar, caption=rpar/rpar] | 328 do 構文を用いない場合、以下の様な式になる。 |
306 runEval $ do | 329 |
307 a <- rpar (f x) | 330 \begin{lstlisting}[caption=do構文を使わない場合] |
308 b <- rpar (f y) | 331 test :: Eval (Integer, Integer) |
309 return (a,b) | 332 test = rpar (sum' 0 num) >>= (\a -> |
310 \end{lstlisting} | 333 rpar (sum' num (num*2)) >>= (\b -> |
311 | 334 return (a,b))) |
312 rpar/rpar パターンは即座に return される(図\ref{fig:rpar})。 | 335 \end{lstlisting} |
313 単純に並列に動かしたい時に利用する。 | 336 |
314 | 337 sum' は2つの引数をとって、開始点から終了点までの値をすべて足し合わせる関数である。 |
315 \begin{figure}[!htbp] | 338 並列処理に負荷を与えるために使う。 |
316 \begin{center} | 339 ifで、開始点が終了点を超えてないか調べ、超えてなければ再帰的に呼び出して足し合わせを行う。 |
317 \includegraphics[scale=0.6]{./images/rpar_rpar.pdf} | 340 |
318 \end{center} | 341 test で返ってくる型は、Eval (Integer, Integer)で、その後 runEval 関数を適用することで、(Integer, Integer)となる。 |
319 \caption{rpar/rpar パターン} | 342 そして最後にprint で出力される。 |
320 \label{fig:rpar} | 343 |
321 \end{figure} | 344 Haskell は遅延評価を行うため、必要となるまで式の評価が遅延される。 |
322 | 345 今回の場合、最後のprintがなければそもそも計算が行われない。 |
323 \begin{lstlisting}[label=rpar/rseq/rseq, caption=rpar/rseq/rseq] | 346 |
324 runEval $ do | 347 並列に動くように処理を分割した後は、Haskell の遅延評価に気をつける必要がある。 |
325 a <- rpar (f x) | 348 値が必要となるprintなどを行えば、並列に実行可能な部分が並列に実行される。 |
326 b <- rseq (f y) | 349 |
327 rseq a | 350 rpar を使用する際に気をつけるのは、別の計算の値に依存する計算がある場合、その2つの計算は並列実行できないということである。 |
351 例えば、以下のような場合は並列実行ができない。 | |
352 | |
353 \begin{lstlisting}[caption=前の計算に依存した計算] | |
354 test2 :: Eval (Integer, Integer) | |
355 test2 = do | |
356 a <- rpar (sum' 0 num) | |
357 b <- rpar (sum' num (if a < num then a else (num*2))) | |
328 return (a,b) | 358 return (a,b) |
329 \end{lstlisting} | 359 \end{lstlisting} |
330 | 360 |
331 rpar/rseq/rseq パターンは、f x および f y の結果を待ってから return する(図\ref{fig:rseq})。 | |
332 他の処理が結果を必要としている時に利用する。 | |
333 | |
334 \begin{figure}[!htbp] | |
335 \begin{center} | |
336 \includegraphics[scale=0.6]{./images/rpar_rseq.pdf} | |
337 \end{center} | |
338 \caption{rpar/rseq/rseq パターン} | |
339 \label{fig:rseq} | |
340 \end{figure} | |
341 | |
342 rparは非常にコストが低いため、リストに対してmapするといった適用の仕方でも充分な並列性が出る。 | |
343 そのための関数 parMap も、 Control.Parallel.Strategies モジュールには用意されている。 |