メタプログラミングで段階制御を一括管理

 段階制御は便利な機能ですが,個々のINLINE指示文やRULES指示文に対して指定していくと,多くの定義を書き換えなければならない場合に誤りが混入する可能性があります。そこで第10回および第24回のコラムで紹介したData Parallel HaskellのDPHライブラリでは,C言語のプリプロセサを用いた段階制御の一括管理を行っています。

 DPHライブラリでは,まず以下のdph-baseパッケージのfusion-phases.hファイルで,インライン化と書き換え規則の適用を行うタイミングや意図を示したマクロを用意しています。

#define INLINE_U       INLINE
#define INLINE_UP      INLINE
#define INLINE_STREAM  INLINE [1]
#define INLINE_DIST    INLINE [1]
#define INLINE_PA      INLINE
#define INLINE_BACKEND INLINE [2]

#define PHASE_PA
#define PHASE_BACKEND   [2]
#define PHASE_DIST      [1]
#define PHASE_STREAM    [1]

 これらのマクロで一括管理を行い,インライン化が行われるタイミングを制御しています(参考リンク1参考リンク2)。

{-# LANGUAGE CPP #-}
#include "fusion-phases.h"
module Data.Array.Parallel.Unlifted.Sequential.Flat.Permute (
  permuteU, permuteMU, bpermuteU, bpermuteDftU, reverseU, updateU,
  atomicUpdateMU
) where
~ 略 ~
bpermuteDftU :: UA e
             => Int                             -- ^ length of result array
             -> (Int -> e)                      -- ^ initialiser function
             -> UArr (Int :*: e)                -- ^ index-value pairs
             -> UArr e
{-# INLINE_U bpermuteDftU #-}
bpermuteDftU n init = updateU (mapU init . enumFromToU 0 $ n-1)
atomicUpdateMU :: UA e => MUArr e s -> UArr (Int :*: e) -> ST s ()
{-# INLINE_U atomicUpdateMU #-}
atomicUpdateMU marr upd = updateM writeMU marr (streamU upd)

~ 略 ~

updateU :: UA e => UArr e -> UArr (Int :*: e) -> UArr e
{-# INLINE_U updateU #-}
updateU arr upd = update (streamU arr) (streamU upd)
update :: UA e => Stream e -> Stream (Int :*: e) -> UArr e
{-# INLINE_STREAM update #-}
update s1@(Stream _ _ n) !s2 = newDynU n (\marr ->
  do
    i <- unstreamMU marr s1
    updateM writeMU marr s2
    return i
  )

{-# LANGUAGE CPP #-}

#include "fusion-phases.h"

module Data.Array.Parallel.Lifted.Selector
where

~ 略 ~

indicesSel2 :: Sel2 -> U.Array Int
{-# INLINE_BACKEND indicesSel2 #-}
indicesSel2 (Sel2 tags idxs na nb) = idxs

elementsSel2_0 :: Sel2 -> Int
{-# INLINE_BACKEND elementsSel2_0 #-}
elementsSel2_0 (Sel2 tags idxs na nb) = na

elementsSel2_0# :: Sel2 -> Int#
{-# INLINE elementsSel2_0# #-}
elementsSel2_0# (Sel2 _ _ (I# na#) _) = na#

elementsSel2_1 :: Sel2 -> Int
{-# INLINE_BACKEND elementsSel2_1 #-}
elementsSel2_1 (Sel2 tags idxs na nb) = nb

 「#include "fusion-phases.h"」でfusion-phases.hファイルのマクロを読み込み,実際の指示文と段階制御に展開していることがわかります。

 GHC 6.10.4までは段階制御のメタプログラミングは完全にCプリプロセサに依存していましたが,GHC 6.12.1からは段階制御のメタプログラミングにTemplate Haskellを使うこともできるようになりました(参考リンク1参考リンク2)。この連載ではTemplate Haskellについて説明していないので,ここでは具体的な方法は示しません。とりあえず,Cプリプロセサ以外の方法もあるということを頭の片隅に入れておいてください。


著者紹介 shelarcy

 2010年1月31日に開催された並列プログラミングカンファレンス(通称:並カン)で,Haskellの並列プログラミングの現状について話しました(プレゼン資料)。

 本連載でも,第10回から第14回にかけて並列プログラミングや並行プログラミングについて説明しました。ですが,その時点では並列プログラミングのためのツール周りの環境が整っていなかったこともあり,Haskellでの並列プログラミングについて一通り説明するだけで終わってしまいました。今後,どうすれば並列化によってプログラムの処理速度が向上するか,どこがプログラムの並列化のボトルネックになるか,といった実践的な事柄を説明する必要があると考えています。

 もっとも,並列処理を行うプログラムの効率化は,逐次処理での効率化の延長線上にあります。なので,まずは逐次処理での効率化について押さえておく必要があります。この連載では,まず逐次処理での効率化の方法をきちんと説明し,その後,並列処理での効率化に進みたいと思っています。