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 モジュールには用意されている。